linux 字符设备——驱动框架(二)

字符设备驱动框架

(前言)

前面编写了一个简单的linux字符设备驱动。接下来简单地来看一下字符设备驱动的框架。

一、重要的数据结构

在linux 字符设备里,有三个比较重要的数据结构,分别是:struct file_operationsstruct filestruct inode
下面来简单的说明一下

1、struct file_operations

这个结构体是相当于设备的驱动了,实现了字符设备的相关操作函数

/* 只介绍部分struct file_operations结构体的成员 */
struct file_operations {
    /* struct module 这个成员不是一个操作; 它是一个指向拥有这个结构体的模块指针。
     * 这个成员用来在它还被使用时阻止模块被卸载。一般初始化为 THIS_MODULE,
     */
    struct module *owner;
        
    /* llseek 函数用来改变文件中当前的读/写的位置,如果这个函数指针是NULL,内核中
     * 缺省的实现通过修改filp->f_pos进行移位
     */
    loff_t (*llseek) (struct file *, loff_t, int);

    /* read函数用来从设备中获取数据。如果这个函数指针是NULL,以 -EINVAL 失败返回给
     *  read 系统调用,一个非负返回值代表了成功读取的字节数。
     */
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

    /* write函数用来发送数据给设备。如果这个函数指针是NULL,以 -EINVAL 失败返回给
     *  write 系统调用,一个非负返回值代表了成功读取的字节数。
     */
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    /* poll 函数是: poll, epoll 和 select 这三个系统调用的后端。这三个系统调用都用作查询对一个
     * 或者多个文件描述符的读或写是否会阻塞。poll函数应返回一个位掩码指示是否非阻塞的读或
     * 者写是可能的。并且给内核提供信息,用来使调用的的进程睡眠,直到读写操作变为可能。
     * 如果一个驱动的 poll 指针为 NULL, 设备假定为不阻塞地可读可写。
     */
    unsigned int (*poll) (struct file *, struct poll_table_struct *);

     /* ioctl 函数 为 ioctl() 系统调用提供了发出设备特定命令的方法
      * 如果一个驱动的 ioctl 指针为 NULL, 对于任何未事先定义的请
      * 求返回 -ENOTTY, 系统调用返回一个错误
      */
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

     /* mmap 用来请求将设备内存映射到进程的地址空间, 
      * 如果这个指针是 NULL, mmap 系统调用返回 -ENODEV.
      */
    int (*mmap) (struct file *, struct vm_area_struct *);
     
     /* open成员是 open 系统调用的后端,通常是对设备文件进行的第一个操作
      * 如果这个指针是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
      */
    int (*open) (struct inode *, struct file *);

     /* release成员是 close系统调用的后端,在文件结构被释放时引用这个操作。
      * 如同open,release成员也可以为NULL。
      */
    int (*release) (struct inode *, struct file *);

     /* fasync这个操作用来通知设备它的 FASYNC 标志的改变(异步通知)
      * 如果这个成员是 NULL, 驱动不支持异步通知。
      */
    int (*fasync) (int, struct file *, int);
};

2、struct file

这个结构体描述的是打开的一个文件,每打开一个文件就会有一个struct file结构体来进行管理

/* 只介绍部分struct file结构体的成员 */
struct file {

    /* 文件模式确定文件是可读的或者是可写的(或者都是),
     *  通过位 FMODE_READ 和FMODE_WRITE 描述
     */
    mode_t          f_mode;

    /*  文件当前的读写位置. 如果驱动需要知到文件中的当前位置,可以读这个值,但是不应该改变它。
     *  在fops 的 read 和 write 以最后的一个参数传入,来代替直接使用f_pos成员,在fops的llseek方法中
     *  通过改变f_pos 成员来改变,文件的读写位置
     */
    loff_t          f_pos;

    /*  文件的标志。例如 O_REONLY, O_NONBLOCK, 和 O_SYNC。驱动通过检查O_NONBLOCK标志
     *  来查看是否请求非阻塞操作,其他标志一般比较少用。
     */
    unsigned int        f_flags;

    /* 和文件关联的操作*/
    const struct file_operations    *f_op;

    /* 这个成员通常用来指向分配的数据,通常在fops->open 成员函数里面指定。
     * 如果使用了这个成员必须在fops->release 成员中释放。
     */
    void            *private_data;
};

3、struct inode

inode 结构用来描述磁盘上一个文件。inode 结构包含大量关于文件的信息,但这个结构只有 2 个成员对于编写驱动代码有用

/* 只介绍部分struct inode结构体的成员 */
struct inode {
    /* 对于代表设备文件的节点, 这个成员包含实际的设备编号. */
    dev_t    i_rdev;
    union {
        struct pipe_inode_info  *i_pipe;
        /* 当节点指的是一个块设备文件时,代表块设备。*/
        struct block_device     *i_bdev;
        /* 当节点指的是一个字符设备文件时,代表字符设备。*/
        struct cdev             *i_cdev;
    };
}

二、linux 源代码框架分析

系统调用的到内核驱动的调用过程如下(以 open 系统调用为例,其他的系统调用类似)
(系统调用的入口函数在 kernel/calls.S 这个文件里面)
  sys_open
     do_sys_open
       fd = get_unused_fd(); //找到一个本进程没有使用的文件描述符
       struct file *f = do_filp_open(dfd, tmp, flags, mode); // 构造和设置struct file 结构体
         nameidata_to_filp(&nd, flags);
           __dentry_open(nd->dentry, nd->mnt, flags, filp, NULL);
             if (!open && f->f_op)//open是一个函数指针作为__dentry_open 的第五个参数
               open = f->f_op->open; //获取fops的open成员
             if (open) {
               error = open(inode, f); //调用函数open函数
             }

三、框架图

<念念有余>这位博主有一张比较详细的框架图,所以直接引用过来了

猜你喜欢

转载自www.cnblogs.com/gulan-zmc/p/11519066.html