美文网首页
Linux 内核学习(5)---- 字符设备驱动操作函数

Linux 内核学习(5)---- 字符设备驱动操作函数

作者: 特立独行的佩奇 | 来源:发表于2023-04-10 21:41 被阅读0次

    file_operation 接口实现

    open close接口的实现
    int (*open) (struct inode *, struct file *);
    int (*release) (struct inode *, struct file *);
    

    open release 操作接口比较简单

    read 接口实现
    ssize_t (*read) (struct file * filp, char __user *buf, size_t count, loff_t * f_pos);
    

    struct file * filp:打开设备节点分配的 struct file 类型
    char __user * buf: 待写入所读取数据的用户空间缓冲区指针
    size_t count:待读取数据字节数
    loff_t f_pos:待读取数据文件位置,读取完成后根据实际读取字节数重新定位

    __user :是一个空的宏,主要用来显示的告诉程序员它修饰的指针变量存放的是用户空间的地址
    如果该操作为空,将使得read系统调用返回负EINVAL失败,正常返回实际读取的字节数

    write 接口实现
    ssize_t (*write) (struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
    

    struct file *filp: 待操作的设备文件file结构体指针
    const char __user buf: 待写入所读取数据的用户空间缓冲区指针
    size_t count:待写入数据字节数
    loff_t * f_pos:待写入数据文件位置,读取完成后根据实际读取字节数重新定位

    ioctl 接口实现
    long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)
    long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
    

    kernel 2.6.35 及之前的版本中struct file_operations 一共有3个ioctl :ioctl,unlocked_ioctl和compat_ioctl 现在只有unlocked_ioctl和compat_ioctl 了
    在kernel 2.6.36 中已经完全删除了struct file_operations 中的ioctl 函数指针,取而代之的是unlocked_ioctl
    应用层调用 ioctl 的方式

    int ioctl(int fd, int cmd, ...);
    参数:
    fd:打开设备文件的时候获得文件描述符 
    cmd:第二个参数:给驱动层传递的命令,需要注意的时候,驱动层的命令和应用层的命令一定要统一
    第三个参数: "..."在C语言中,很多时候都被理解成可变参数。
    

    当我们通过ioctl调用驱动层xxx_ioctl的时候,有三种情况可供选择:

    1. 不传递数据给ioctl
    2. 传递数据给ioctl,希望它最终能把数据写入设备(例如:设置Framebuffer 的显示信息)
    3. 调用_ioctl希望获取设备的硬件参数(例如:获取当前串口设备的波特率)
      这三种情况中,有时候需要从用户空间读取数据,有时候需要从内核空间拷贝数据,有时候不需要传递数据,
      用"..."来表示,可以带一个参数,或者不带参数
    ioctl cmd 值的定义

    include/uapi/asm-generic/ioctl.h


    ioctl_cmd.jpg
    #define _IOC(dir,type,nr,size) \
        (((dir)  << _IOC_DIRSHIFT) | \ //30
         ((type) << _IOC_TYPESHIFT) | \ //8
         ((nr)   << _IOC_NRSHIFT) | \ //0
         ((size) << _IOC_SIZESHIFT)) //16
    
    #ifndef __KERNEL__
    #define _IOC_TYPECHECK(t) (sizeof(t))
    #endif
    
    /* used to create numbers */
    #define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
    #define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
    #define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
    #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
    #define _IOR_BAD(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))
    #define _IOW_BAD(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
    #define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
    
    /* used to decode ioctl numbers.. */
    #define _IOC_DIR(nr)        (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
    #define _IOC_TYPE(nr)       (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
    #define _IOC_NR(nr)     (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
    #define _IOC_SIZE(nr)       (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
    

    type:一般为magincNumber nr:一般为递增的序列号 size:传输数据的size
    cmd 是一个unsigned int 类型的整形数,大小为32Bit

    • cmd 的高位 2Bit cmd[31:30] 是数据的传输方向,可以是 _IOC_READ,_IOC_WRITE或者IOC_READ|_IOC_WRITE和_IOC_NONE
    • cmd[29:16] 最多是14bit,表示数据的大小
    • cmd[15:8] 命令的类型,一般使用一个固定的数值,称为 magicnumber
    • cmd[7:0] 序列号,一般表示第几个命令,随着命令数递增

    通过 linux 中提供的宏,我们只需要定义 MagicNumber,命令序号和数据字段(宏中会自动使用sizeof)就好了

    如何检查命令

    _IOC_TYPE(nr) 来判断应用程序传下来的命令type是否正确
    _IOC_DIR(nr)来得到命令是读还是写,然后再通过宏access_ok(type,addr,size)来判断用户层传递的内存地址是否合法

    内核空间和用户空间的内存拷贝

    include/asm-generic/uaccess.h

    static inline int copy_from_user(void *to, const void __user volatile *from,unsigned long n)
    static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
     /* to:目标地址(用户空间)
      from:源地址(内核空间)
      n:将要拷贝数据的字节数*/
    
    #define put_user(x, ptr)                    \
    ({                              \
        void *__p = (ptr);                  \
        might_fault();                      \
        access_ok(VERIFY_WRITE, __p, sizeof(*ptr)) ?        \
            __put_user((x), ((__typeof__(*(ptr)) *)__p)) :  \
            -EFAULT;                    \
    })
    /*
      data:可以是字节、半字、字、双字类型的内核变量
      ptr:用户空间内存指针
    */
    
    #define get_user(x, ptr)                    \
    ({                              \
        const void *__p = (ptr);                \
        might_fault();                      \
        access_ok(VERIFY_READ, __p, sizeof(*ptr)) ?     \
            __get_user((x), (__typeof__(*(ptr)) *)__p) :    \
            ((x) = (__typeof__(*(ptr)))0,-EFAULT);      \
    })
    /*
      data:可以是字节、半字、字、双字类型的内核变量
      ptr:用户空间内存指针
    */
    

    Linux 提供 copy_from_user,copy_to_user,put_user 和 get_user宏来和用户空间交换数据
    使用这些函数和用户空间交互时,内核会做参数检查,比如指针指向的区域是否属于用户空间,是否属于用户的当前进程,读和写的内存必须由相应的权限

    字符设备注册函数

    内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()
    代码位置: include/linux/fs.h kernel/fs/char_dev.c

    static inline int register_chrdev(unsigned int major, const char *name,
                      const struct file_operations *fops)
    {
        return __register_chrdev(major, 0, 256, name, fops);
    }
    
    static inline void unregister_chrdev(unsigned int major, const char *name)
    {
        __unregister_chrdev(major, 0, 256, name);
    }
    
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
    
    int  unregister_chrdev_region(dev_t, unsigned);
    

    register_chrdev 可以直接传入 file_opeations 结构体,本质上相当于将 cdev 的操作在函数内部实现了
    register_chrdev_region 可以指定主设备号,但需要配合cdev 结构体一起使用
    alloc_chrdev_region 动态分配主设备号,传出dev_t 的结构

    register_chrdev_region(dev_t first,unsigned int count,char *name)
    

    First: 主设备号,要分配的设备编号范围的初始值, 这组连续设备号的起始设备号
    Count: 连续编号范围. 是这组设备号的大小(也是次设备号的个数)
    Name: 编号相关联的设备名称. (/proc/devices);本组设备的驱动名
    正确的时候返回 0

    register_chrdev_region 需要和 cdev 的相关函数一起使用

    int alloc_chrdev_region(dev_t* dev ,unsigned int first minor,unsigned int count,char *name)
    

    dev_t* dev 是传出的参数 用于获取 dev_t 数据结构 自动获取主次设备号
    minor 次设备号
    count 请求连续分配的设备个数
    name 出现在 /proc/devices 和 sysfs 中

    cdev 操作相关函数

    include/linux/cdev.h
    用于分配,初始化一个cdev 结构,将struct file_operations赋值给它,最后将它和 dev_t 结构绑定

    struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;
        struct list_head list;
        dev_t dev;
        unsigned int count;
    };
    
    void cdev_init(struct cdev *, const struct file_operations *);
    
    struct cdev *cdev_alloc(void);
    
    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);
    

    一般用法如下:

    demo_cdev = cdev_alloc();  
    cdev_init(demo_cdev,&Mstar_demo_driver);  
    demo_cdev->owner = THIS_MODULE;  
    
    ret = cdev_add(demo_cdev,demo_devt,1);  
    if(ret) 
    {  
        printk("cdev create error!\n");  
        unregister_chrdev_region(demo_devt,1);  
        return ret;  
    } 
    

    相关文章

      网友评论

          本文标题:Linux 内核学习(5)---- 字符设备驱动操作函数

          本文链接:https://www.haomeiwen.com/subject/reswddtx.html