标记化结构初始化方法
内核中结构体的赋值常使用标准C的标记化结构初始化方法,这在之前的用户层代码并不常见。
static struct file_operations fops ={
.owner = THIS_MODULE,
.open = hello_dev_open,
.release = hello_dev_release,
.read = hello_dev_read,
.write = hello_dev_write,
};
优点有以下三点:
- 首先,标记传参不用理会参数传递的顺序,程序员不用记忆参数的顺序;
- 我们可以选择性传参,在传统C语言顺序传参中,如果你只想对第三个变量进行初始化,那么你不得不给第一个, 第二个参数进行初始化,而有时候一个变量并没有很合适的默认值,而使用标记初始化法,你可以相当自由地对你有把握的参数进行初始化;
- 扩展性更好,如果你要在该结构体中增加一个字段,传统方式下,为了考虑代码修改量,你最好将新添加的字段放在这个结构体的最后面,否则你将要面对大量且无趣的修改。
file_operation
《LINUX设备驱动程序》第三版P53~55
该结构体为一个函数指针(回调函数)的集合。用户进程利用在对设备文件进行诸如read/write操作的时候,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理。
struct file_operations {
struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES
loff_t (*llseek) (struct file *, loff_t, int);//用来修改文件当前的读写位置
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//从设备中同步读取数据。用户层调用read,直接转到该函数指针指向的函数
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//向设备发送数据
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的读取操作
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的写入操作
int (*readdir) (struct file *, void *, filldir_t);//仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替
int (*mmap) (struct file *, struct vm_area_struct *); //用于请求将设备内存映射到进程地址空间
int (*open) (struct inode *, struct file *); //打开
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *); //关闭
int (*fsync) (struct file *, struct dentry *, int datasync); //刷新待处理的数据
int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据
int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
常用的有(我的github有例程)open,release,read,write,mmap
file
《LINUX设备驱动程序》第三版P57~59
区别用户空间的file。FILE在用户空间定义且不会出现在内核代码中,struct file是一个内核结构,不会出现在用户程序中。file结构代表一个打开的文件,由内核在open时创建,并传递给在该文件上进行操作的所有函数。file_operations内的回调函数的第一个参数都是file*
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
//与文件相关的操作
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count;
unsigned int f_flags; //文件标志,如O_RDONLY,O_NONBLOCK,O_SYNC检查用户请求是否是非阻塞的操作
fmode_t f_mode; //文件模式 通过FMODE_READ FMODE_WRITE表示文件可读或可写
struct mutex f_pos_lock;
loff_t f_pos; //当前的读/写位置
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
/*open系统调用在调用open驱动方法前把这个指针置为NULL,驱动程序可以把这个指针用于任何目的或者忽略他。
但是一定要在内核销毁file结构前在release方法中释放内存。这是保存状态信息非常有用的资源。*/
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
} __randomize_layout
重要的有 f_pos
inode
内核用inode结构在内部表示文件,它和file不同,后者表示打开的文件描述符,对于单个文件,可能会有许多表示打开的文件描述符的file结构,但它们都指向单个inode结构。
inode结构中包含了大量有关文件的信息,作为常规,只有下面两个字段对驱动程序有用:
dev_t i_rdev; //对表示设备文件的inode结构,包含了真正的设备编号。
struct cdev *i_cdev;//表示字符设备的内核内部数据结构,当inode指向一个字符设备文件,该字段包含指向struct cdev结构的指针
下面两个宏可用来从inode中获得主设备号和次设备号:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);