🔥博客主页:PannLZ
🎋系列专栏:《Linux系统之路》
设备文件操作
可以在文件上执行的操作取决于管理文件的设备驱动程序。这样的操作在内核中定义为struct file_operations
的实例。struct file_operations
定义了一组回调函数,用于处理文件上的所有用户空间系统调用。
举个例子,如果想让用户在设备文件上执行write操作,必须在驱动中实现与write函数对应的回调函数,并把它添加到绑定在设备上的struct file_operations
中。请看下面的文件操作结构:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t,int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char__user *, size_t, loff_t*);
unsigned int (*poll) (struct file *, structpoll_table_struct *);
int (*mmap) (struct file *, structvm_area_struct *);
int (*open) (struct inode *, struct file *);
long (*unlocked_ioctl) (struct file *,unsigned int, unsigned long);
int (*release) (struct inode *, struct file*);
int (*fsync) (struct file *, loff_t, loff_t,int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, structfile_lock *);
int (*flock) (struct file *, int, structfile_lock *);
[...]
};
这些方法的完整描述请参阅内核源码include/linux/fs.h
。
当用户代码在指定文件上调用与文件相关的系统调用时,内核会查找负责这个文件的驱动程序(尤其是创建该文件的驱动程序),定位它的struct file_operations
结构,并检查和该系统调用匹配的方法是否已经定义。如果定义了,就运行它;如果未定义,则根据系统调用不同返回不同的错误码。
内核中的文件表示
内核把文件描述为**inode结构
**(不是文件结构)的实例,inode
结构在include/linux/fs.h
中定义:
struct inode {
[...]
struct pipe_inode_info *i_pipe; /* 如果这是Linux内核管道,则设置并使用 */
struct block_device *i_bdev; /* 如果这是块设备,则设置并使用 */
struct cdev *i_cdev; /* 如果这是字符设备,则设置并使用 */
[...]
}
struct inode
是文件系统的数据结构,它只与操作系统相关,用于保存文件(无论它的类型是字符、块、管道等)或目录(从内核的角度来看,目录也是文件,是其他文件的入口点)信息。
struct file
结构(也在include/linux/fs.h
中定义)是更高级的文件描述,它代表内核中打开的文件,依赖于低层的struct inode数据结构:
struct file {
[...]
struct path f_path; /* 文件路径 */
struct inode *f_inode; /* 与此文件相关的inode */
const struct file_operations *f_op; /* 可以在此文件上执行的操作 */
loff_t f_pos; /* 此文件中光标的位置 */
/* 需要tty驱动程序等 */
void *private_data; /* 驱动程序可以设置的私有数据,以便在文件操作之间共享,这可以指向任何结构*/
[...]
}
struct inode
和struct file
的区别在于,inode不跟踪文件的当前位置和当前模式,它只是帮助操作系统找到底层文件结构的内容(管道、目录、常规磁盘文件、块/字符设备文件等)。而struct file
则是一个基本结构(它实际上持有一个指向struct inode
的指针),它代表打开的文件,并且提供一组函数,它们与底层文件结构上执行的方法相关,这些方法包括open、write、seek、read、select等。所有这一切都强化了UNIX系统的哲学:一切皆是文件。
换句话说,struct inode代表内核中的文件,struct file描述实际打开的文件。同一个文件打开多次时,可能会有不同的文件描述符,但它们都指向同一个inode。
分配和注册字符设备
字符设备在内核中表示为struct cdev
的实例。在编写字符设备驱动程序时,目标是最终创建并注册与struct file_operations
关联的结构实例,为用户空间提供一组可以在该设备上执行的操作(函数)。为了实现这个目标,必须执行以下几个步骤。
(1)使用alloc_chrdev_region()保留一个主设备号和一定范围的次设备号。
(2)使用class_create()创建自己的设备类,该函数在/sys/class中定义。
(3)创建一个struct file_operation(传递给cdev_init),每一个设备都需要创建,并调用call_init和cdev_add()注册这个设备。
(4)调用device_create()创建每个设备,并给它们一个合适的名字。这样,就可在/dev目录下创建出设备。
#define EEP_NBANK 8
#define EEP_DEVICE_NAME "eep-mem"
#define EEP_CLASS "eep-class"
struct class *eep_class;
struct cdev eep_cdev[EEP_NBANK];
dev_t dev_num;
static int __init my_init(void)
{
int i;
dev_t curr_dev;
/* 为EEP_NBANK设备请求内核*/
alloc_chrdev_region(&dev_num, 0, EEP_NBANK,EEP_DEVICE_NAME);
/* 创建设备的类,在/sys/class中可见 */
eep_class = class_create(THIS_MODULE,EEP_CLASS);
/* 每个eeprom bank表示为一个字符设备(cdev)*/
for (i = 0; i < EEP_NBANK; i++) {
/* 将file_operations绑定到cdev */
cdev_init(&my_cdev[i], &eep_fops);
eep_cdev[i].owner = THIS_MODULE;
/* 用于将cdev添加到核心的设备号 */
curr_dev = MKDEV(MAJOR(dev_num),MINOR(dev_num) + i);
/* 让用户访问设备 */
cdev_add(&eep_cdev[i], curr_dev, 1);
/*
* 为每个设备的/dev/ ep-mem0、/dev/ ep-mem1
创建一个设备节点。由于这里使用了类,
* 因此还可以在/sys/class/ ep-class下查看设备
*/
device_create(eep_class,
NULL, /* 没有父设备 */
curr_dev,
NULL, /* 没有额外的数据*/
EEP_DEVICE_NAME "%d", i);
/* eep-mem[0-7] */
}
return 0;
}