Linux 驱动程序入门

驱动是具有入口和出口的一组方法的集合,这一组方法才是驱动的核心内容。
对于字符设备驱动程序,最核心的就是 file_operation 结构,这个结构实际上是提供给虚拟文件系统 [ VFS ] 的文件接口,它的每一个成员函数一般都对应一个系统调用。用户进程利用系统调用对设备文件进行诸如操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序。

可根据这篇 https://www.cnblogs.com/dl04301201/p/10098864.html 字符设备驱动来理解本文的讲解,并根据本文的讲解来改用 cdev 结构进行写驱动。

看图来理清一下思路 :
在这里插入图片描述
在这里插入图片描述

理解 : 系统调用通过设备文件的主设备号找到相应的设备驱动程序
从下图中,/dev 目录下的设备文件可知,agpgart 的主设备号为 10 次设备号为 175
而且设备文件中,主设备号时可以相同的,次设备号的唯一的。
主设备号跟次设备号就可以用来识别一个设备文件。像通过身份证号码就可以识别一个人一样。
首字符c b 是标识符, c 表示字符设备, b 表示块设备

file_operations结构

摘自 Linxu 4.17.14,,在 /include/linux/fs.h中定义。用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的 事务的函数的地址。

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 *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);
    __poll_t (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    unsigned long mmap_supported_flags;
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    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, 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 **, void **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
    void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
    unsigned (*mmap_capabilities)(struct file *);
#endif
    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,loff_t, size_t, unsigned int);
    int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,u64);
    ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,u64);
} __randomize_layout;

file_opration 简单的运用 :
应用层系统调用与驱动层 struct file_operation fops 的调用核心。

    struct file_operations fops = {
       .read = device_read,
       .write = device_write,
       .open = device_open,
       .release = device_release
    };

注册字符设备的机制

{

使用 register_chrdev 注册字符设备

注册字符设备函数 :int register_chrdev(unsigned int major, char name, struct file_operations fops);

参数 major 如果等于 0,则表示采用系统动态分配的主设备号。如果不等于 0,则需要通过查询未用的设备号来为函数传参数

使用 register_chrdev 注销字符设备

注销字符设备函数 : int unregister_chrdev(int major, const char *name);
}

使用 cdev_add 注册字符设备

{

在 Linux 内核中的字符设备用 cdev 结构来描述,其结构如下,摘自 Linxu 4.17.14

struct cdev {
    struct kobject kobj;
    struct module *owner;                //所属模块,一般为 THIS_MODULE
    const struct file_operations *ops;   //文件操作结构,即上文中的 file_operations 结构
    struct list_head list;
    dev_t dev;                           //设备号
    unsigned int count;
} __randomize_layout;

对 cdev 结构的操作,内核提供了一组函数

void cdev_init(struct cdev *, const struct file_operations *);    //初始化一个 cdev 结构
struct cdev *cdev_alloc(void);                                    //为 cdev 结构分配内存
void cdev_put(struct cdev *p);                     //减少使用计数
int cdev_add(struct cdev *, dev_t, unsigned);         //注册设备,通常发生在驱动模块的加载函数中
void cdev_set_parent(struct cdev *p, struct kobject *kobj); 
int cdev_device_add(struct cdev *cdev, struct device *dev);
void cdev_device_del(struct cdev *cdev, struct device *dev);
void cdev_del(struct cdev *);                       //注销设备,通常发生在驱动模块的卸载函数中
void cd_forget(struct inode *);

使用 cdev 结构注册设备的一般过程如下。
以动态分配设备号为例:
申请设备号
分配设备结构
初始化设备结构
添加字符设备

无论是用 register_chrdev 还是用 cdev 注册字符设备都是可以的。 register_chrdev机制出现在 linux 2.6之前的内核版本中,而 cdev 出现在 linux 2.6及之后的内核版本中,不过 linux 2.6及之后的内核版本依然兼容 register_chrdev 机制。
}

字符设备的读写

{
在 Linux 有个最核心的思想,一切皆为文件。字符设备依然不例外。字符设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。

最后根据 xxx_read(),xxx_write() 内容进行硬件操作。’
}

猜你喜欢

转载自blog.csdn.net/sd09044901guic/article/details/84989054