1.字符设备结构体
12 struct cdev {
13 >---struct kobject kobj;
14 >---struct module *owner;//THIS_MODULE
15 >---const struct file_operations *ops;//支持的文件操作
16 >---struct list_head list;
17 >---dev_t dev;//设备号
18 >---unsigned int count;
19 };
设备号
MAJOR(dev)//获取主设备号
MINOR(deb)//获取次设备号
MKDEV(major,minor)//合成设备号
注册设备号
int register_chrdev_region(dev, count, name)//已有设备号,向系统注册
int alloc_chrdev_region(&dev, baseminor, count, name);
//dev变量:设备号,baseminor:次设备号起始值,count:设备个数 name:设备名
释放设备号:void unregister_chrdev_region (dev, count)
设备结构体操作
21 void cdev_init(struct cdev *, const struct file_operations *);//初始化cdev结构体,赋值fops
22
23 struct cdev *cdev_alloc(void);//动态申请一个cdev内存,kzalloc分配
24
25 void cdev_put(struct cdev *p);//cdev_alloc后 释放
26
27 int cdev_add(struct cdev *, dev_t, unsigned);//cdev结构体准备ok,调用该函数注册,提供设备号及设备号数目(一般是1)
28
29 void cdev_del(struct cdev *);//移除字符设备
//定义设备结构体变量,封装cdev
struct xxx_dev_t {
struct cdev dev;
...
} xxx_dev;
//模块加载函数
static int __init xxx_init(void)
{
...
cdev_init(&xxx_dev.cdev, %xxx_fops);//初始化cdev
//获取字符设备号
if (xxx_major) {
//已有设备号,注册
register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
else {
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
}
cdev_add(&xxx_dev.cdev, xxx_dev_no, 1);
...
}
//模块卸载函数
static void __exit xxx_exit(void)
{
unregiser_chrdev_region(xxx_dev_no, 1);//释放设备号
cdev_del(&xxx_dev.cdev);//注销设备
}
file_operations结构体
1651 struct file_operations {
1652 >---struct module *owner;
1653 >---loff_t (*llseek) (struct file *, loff_t, int);
1654 >---ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1655 >---ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1656 >---ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
1657 >---ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
1658 >---int (*iterate) (struct file *, struct dir_context *);
1659 >---unsigned int (*poll) (struct file *, struct poll_table_struct *);
1660 >---long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//去除大内核锁,驱动自行实现锁机制
1661 >---long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1662 >---int (*mmap) (struct file *, struct vm_area_struct *);
1663 >---int (*open) (struct inode *, struct file *);
1664 >---int (*flush) (struct file *, fl_owner_t id);
1665 >---int (*release) (struct inode *, struct file *);
1666 >---int (*fsync) (struct file *, loff_t, loff_t, int datasync);
1667 >---int (*aio_fsync) (struct kiocb *, int datasync);
1668 >---int (*fasync) (int, struct file *, int);
1669 >---int (*lock) (struct file *, int, struct file_lock *);
1670 >---ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1671 >---unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1672 >---int (*check_flags)(int);
1673 >---int (*flock) (struct file *, int, struct file_lock *);
1674 >---ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1675 >---ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1676 >---int (*setlease)(struct file *, long, struct file_lock **, void **);
1677 >---long (*fallocate)(struct file *file, int mode, loff_t offset,
1678 >--->--->--- loff_t len);
1679 >---void (*show_fdinfo)(struct seq_file *m, struct file *f);
1680 #ifndef CONFIG_MMU
1681 >---unsigned (*mmap_capabilities)(struct file *);
1682 #endif
1683 };
参考:
struct file_operations{
struct module *owner
//第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.
loff_t (*llseek) (struct file *, loff_t, int);
//llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
//用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
//初始化一个异步读 -- 可能在函数返回前不结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
//初始化设备上的一个异步写.
int (*readdir) (struct file *, void *, filldir_t);
//对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
//poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
//ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.
int (*mmap) (struct file *, struct vm_area_struct *);
//mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
int (*open) (struct inode *, struct file *);
//尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
int (*flush) (struct file *);
//flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.
int (*release) (struct inode *, struct file *);
//在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
int (*fsync) (struct file *, struct dentry *, int);
//这个方法是 fsync 系统调用的后端, 用户调用来刷新任何挂着的数据. 如果这个指针是 NULL, 系统调用返回 -EINVAL.
int (*aio_fsync)(struct kiocb *, int);
//这是 fsync 方法的异步版本.
int (*fasync) (int, struct file *, int);
//这个操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述. 这个成员可以是NULL 如果驱动不支持异步通知.
int (*lock) (struct file *, int, struct file_lock *);
//lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
//这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
//这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个. 例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
//sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
//这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中. 这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.[10]
int (*check_flags)(int)
//这个方法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志.
int (*dir_notify)(struct file *, unsigned long);
//这个方法在应用程序使用 fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.