linux 基础复习(9)设备驱动入门
struct inode提供了关于设备文件/dev/driver(假设此设备名为driver)的信息。struct file 提供关于被打开的文件信息,主要用于与文件系统对应的设备驱动程序使用。struct file 较为重要,这里列出了它的定义:
struct file {
mode_t f_mode;/*标识文件是否可读或可写,FMODE_READ或FMODE_WRITE*/
dev_t f_rdev; /* 用于/dev/tty */
off_t f_pos; /* 当前文件位移 */
unsigned short f_flags; /* 文件标志,如O_RDONLY、O_NONBLOCK和O_SYNC */
unsigned short f_count; /* 打开的文件数目 */
unsigned short f_reada;
struct inode *f_inode; /*指向inode的结构指针 */
struct file_operations *f_op;/* 文件索引指针 */
};
设备驱动程序主要组成
(1)设备注册
设备注册使用函数register_chrdev,调用该函数后就可以向系统申请主设备号,如果register_chrdev操作成功,设备名就会出现在/proc/devices 文件里。
register_chrdev等函数语法要点所需头文件 #i nclude 函数原型 int register_chrdev(unsigned int major, const char *name,struct file_operations *fops) major:设备驱动程序向系统申请的主设备号 如果为0 则系统为此驱动程序动态地分配一个主设备号函数传入值 name:设备名 fops:对各个调用的入口点函数返回值 成功:如果是动态分配主设备号,此返回所分配的主设备号 且设备名就会出现在/proc/devices文函数返回值 件里
出错:-1
(2)设备解除注册
在关闭设备时,通常需要解除原先的设备注册,此时可使用函数unregister_chrdev,此后该设备就会从/proc/devices 里消失。
unregister_chrdev等函数语法要点
所需头文件 #i nclude
函数原型 int unregister_chrdev(unsigned int major, const char *name)
major:设备的主设备号,必须和注册时的主设备号相同。
函数传入值 name:设备名
函数返回值 成功:0,且设备名从/proc/devices文件里消失。
出错:-1
(3)打开设备
打开设备的接口函数是open,根据设备的不同,open函数完成的功能也有所不同,但通常情况下在open函数中要完成如下工作。
· 递增计数器。
· 检查特定设备的特殊情况。
· 初始化设备。
· 识别次设备号
其中递增计数器是用于设备计数的。由于设备在使用时通常会打开较多次数,也可以由不同的进程所使用,所以若有一进程想要关闭该设备,则必须保证其他设备没有使用该设备。因此使用计数器就可以很好地完成这项功能。
这里,实现计数器操作的是用在中定义的3 个宏如下。
· MOD_INC_USE_COUNT:计数器加一。
· MOD_DEC_USE_COUNT:计数器减一。
· MOD_IN_USE:计数器非零时返回真。
另外,当有多个物理设备时,就需要识别次设备号来对各个不同的设备进行不同的操作,在有些驱动程序中并不需要用到。
虽然这是对设备文件执行的第一个操作,但却不是驱动程序一定要声明的操作。若这个函数的入口为NULL,那么设备的打开操作将永远成功,但系统不会通知驱动程序。
(4)释放设备
释放设备的接口函数是release。要注意释放设备和关闭设备是完全不同的。当一个进程释放设备时,其他进程还能继续使用该设备,只是该进程暂时停止对该设备的使用;而当一个进程关闭设备时,其他进程必须重新打开此设备才能使用。
释放设备时要完成的工作如下。
· 递减计数器MOD_DEC_USE_COUNT。
· 在最后一次释放设备操作时关闭设备。
(5)读写设备
读写设备的主要任务就是把内核空间的数据复制到用户空间,或者从用户空间复制到内核空间,也就是将内核空间缓冲区里的数据复制到用户空间的缓冲区中或者相反。这里首先解释一个read和write函数的入口函数,如下表所示。
read、write函数语法要点
所需头文件 #i nclude
函数原型 ssize_t (*read) (struct file *filp, char *buff, size_t count, loff_t *offp)
ssize_t (*write) (struct file *filp, const char *buff, size_t count, loff_t *offp)
filp:文件指针
函数传入值 buff:指向用户缓冲区
count:传入的数据长度
offp:用户在文件中的位置
函数返回值 成功:写入的数据长度
虽然这个过程看起来很简单,但是内核空间地址和应用空间地址是有很大区别的,其中之一就是用户空间的内存是可以被换出的,因此可能会出现页面失效等情况。所以就不能使用诸如memcpy 之类的函数来完成这样的操作。在这里就要使用copy_to_user 或copy_from_user 函数,它们就是用来实现用户空间和内核空间的数据交换的。copy_to_user 和copy_from_user 的格式如下表
所需头文件 #i nclude
函数原型 Unsigned long copy_to_user(void *to, const void *from, unsigned long count)
Unsigned long copy_from_user(void *to, const void *from, unsigned long count)
To:数据目的缓冲区
函数传入值 From:数据源函数传入值 缓冲区
count:数据长度
函数返回值 成功:写入的数据长度
失败:-EFAULT
这两个函数不仅实现了用户空间和内核空间的数据转换,而且还会检查用户空间指针的有效性。如果指针无效,那么就不进行复制
(6)获取内存
在应用程序中获取内存通常使用函数malloc,但在设备驱动程序中动态开辟内存可以有基于内存地址和基于页面为单位两类。其中,基于内存地址的函数有kmalloc,注意的是,kmalloc函数返回的是物理地址,而malloc 等返回的是线性地址,因此在驱动程序中不能使用malloc函数。与malloc()不同,kmalloc()申请空间有大小限制。长度是2的整次方,并且不会对所获取的内存空间清零。
基于页为单位的内存有函数族有如下。
· get_zeroed_page:获得一个已清零页面。
评论