linux字符设备驱动结构
linux内核中使用cdev结构体来描述字符设备,cdev结构体的定义如下:
struct cdev
{
struct kobject kobj; //内嵌的kobject对象
struct module *owner; //所属模块
struct file_operations *ops; //文件操作结构体
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
cdev结构体的dev_t成员定义了设备号,为32位,其中高12位为主设备号,低20位为此设备号。file_operations则定义了字符设备驱动提供给虚拟文件系统的接口函数。
linux内核提供了一组函数用于操作cdev结构体:
void cdev_init(struct cdev *,struct file_operations * );
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *,unsigned dev_t);
void cdev_del(struct cdev * );
cdev_init()函数用于初始化cdev的成员,并建立cdev和file_operation之间的连接。
cdev_alloc函数用于动态申请一个cdev内存。
cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。
在调用cdev_add()函数向系统注册字符设备之前,应首先调用
int register_chrdev_region(dev_t from,unsigned count,const char * name);
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char * name);
前者用于已知起始设备的设备号的情况,后者用于设备号未知,向系统动态申请未被占用的设备用的情况。
释放设备号用:
void unregister_chrdev_region(dev_t from, unsigned count);
file_operation结构体的成员函数是字符设备驱动程序设计的主体内容。
linux字符设备驱动的组成
1.字符设备驱动模块加载与卸载函数(以rtc为例)
module_init(s3c_rtc_init);
static int __init s3c_rtc_init(void)
{
printk(banner);
return platform_driver_register(&s3c2410_rtc_driver);
}
module_exit(s3c_rtc_exit);
static void __exit s3c_rtc_exit(void)
{
platform_driver_unregister(&s3c2410_rtc_driver);
}
2.字符设备驱动的file_operation结构体中成员函数
1 struct file_operations
2 {
3 struct module *owner;
4 // 拥有该结构的模块的指针,一般为THIS_MODULES
5 loff_t(*llseek)(struct file *, loff_t, int);
6 // 用来修改文件当前的读写位置
7 ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*);
8 // 从设备中同步读取数据
9 ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);
10 // 初始化一个异步的读取操作
11 ssize_t(*write)(struct file *, const char _ _user *, size_t,
loff_t*);
12 // 向设备发送数据
13 ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t,
loff_t);
14 // 初始化一个异步的写入操作
15 int(*readdir)(struct file *, void *, filldir_t);
16 // 仅用于读取目录,对于设备文件,该字段为 NULL
17 unsigned int(*poll)(struct file *, struct poll_table_struct*);
18 // 轮询函数,判断目前是否可以进行非阻塞的读取或写入
19 int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned
long);
20 // 执行设备I/O控制命令
21 long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
22 // 不使用BLK文件系统,将使用此种函数指针代替ioctl
23 long(*compat_ioctl)(struct file *, unsigned int, unsigned long);
24 // 在64位系统上,32位的ioctl调用将使用此函数指针代替
25 int(*mmap)(struct file *, struct vm_area_struct*);
26 // 用于请求将设备内存映射到进程地址空间
27 int(*open)(struct inode *, struct file*);
28 // 打开
29 int(*flush)(struct file*);
30 int(*release)(struct inode *, struct file*);
31 // 关闭
32 int(*synch)(struct file *, struct dentry *, int datasync);
33 // 刷新待处理的数据
34 int(*aio_fsync)(struct kiocb *, int datasync);
35 // 异步fsync
36 int(*fasync)(int, struct file *, int);
37 // 通知设备FASYNC标志发生变化
38 int(*lock)(struct file *, int, struct file_lock*);
39 ssize_t(*readv)(struct file *, const struct iovec *, unsigned long,
loff_t*);
40 ssize_t(*writev)(struct file *, const struct iovec *, unsigned long,
loff_t*);
41 // readv和writev:分散/聚集型的读写操作
42 ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t,
void*);
43 // 通常为NULL
44 ssize_t(*sendpage)(struct file *, struct page *, int, size_t,
loff_t *, int);
45 // 通常为NULL
46 unsigned long(*get_unmapped_area)(struct file *,unsigned long,
unsigned long,
47 unsigned long, unsigned long);
48 // 在进程地址空间找到一个将底层设备中的内存段映射的位置
49 int(*check_flags)(int);
50 // 允许模块检查传递给fcntl(F_SETEL...)调用的标志
51 int(*dir_notify)(struct file *filp, unsigned long arg);
52 // 仅对文件系统有效,驱动程序不必实现
53 int(*flock)(struct file *, int, struct file_lock*);
54 };
字符设备驱动的结构如下:
设备驱动程序编写流程
设备驱动程序可以使用模块的方式加载的方式加载到内核中去,驱动开发时时没有main()函数,模块在调用insmod命令时被加载,此时的入口点式init_module()函数,通常在该函数中完成设备的注册。同样,模块在调用rmmod命令时被卸载,此时的入口点是cleanup_module()函数,在该函数中完成设备的卸载。在设备完成注册加载之后,用户的应用程序就可以对该设备进行一定的操作,如open()、read()、write()等,而驱动程序就是用于实现这些操作,在用户应用程序调用相应入口函数时执行相关的操作。
设备功能是由file_operation()函数定义的。
proc文件系统
/proc文件系统是一个伪文件系统,它是一种内核和内核模块用来向进程发送信息的机制。这个伪文件系统让用户可以和内核内部数据结构进行交互,获取有关系统和进程的有用信息,在运行时通过改变内核参数来改变设置。与其他文件系统不同,/proc存在于内存之中而不是在硬盘之中。/proc文件系统体现了内核及进程运行的内容,在加载模块成功后,读者可以通过查看/proc/device文件获得相关设备的主设备号
通常情况下,字符驱动程序主要还是根据linux中已有的模板进行更改。