• 【linux驱动】简单字符设备驱动


    1. 设备文件相关结构体和函数

    一个 Linux 系统,默认最大的主设备号是 255,结构体如下

    // include/linux/fs.h
    #define CHRDEV_MAJOR_HASH_SIZE 255
    
    // /fs/char_dev.c
    static struct char_device_struct {
    	struct char_device_struct *next;
    	unsigned int major;
    	unsigned int baseminor;
    	int minorct;
    	char name[64];
    	struct cdev *cdev;		/* will die */
    } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    设备注册函数

    // include/linux/fs.h
    // 向内核注册了一个字符设备
    static inline int register_chrdev(unsigned int major, const char *name,
    				  const struct file_operations *fops)
    {
    	return __register_chrdev(major, 0, 256, name, fops);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    /**
     * __register_chrdev() - create and register a cdev occupying a range of minors
     * @major: major device number or 0 for dynamic allocation   主设备号或者为 0 就是动态分配
     * @baseminor: first of the requested range of minor numbers 次设备号,register_chrdev 直接使用了 0
     * @count: the number of minor numbers required                    
     * @name: name of this range of devices                      设备名字
     * @fops: file operations associated with this devices       文件结构体(Linux 万物皆文件,靠的就是这个结构体)
     *
     * If @major == 0 this functions will dynamically allocate a major and return
     * its number.
     *
     * If @major > 0 this function will attempt to reserve a device with the given
     * major number and will return zero on success.
     *
     * Returns a -ve errno on failure.
     *
     * The name of this device has nothing to do with the name of the device in
     * /dev. It only helps to keep track of the different owners of devices. If
     * your module name has only one type of devices it's ok to use e.g. the name
     * of the module here.
     */
    int __register_chrdev(unsigned int major, unsigned int baseminor,
    		      unsigned int count, const char *name,
    		      const struct file_operations *fops)
    {
    	struct char_device_struct *cd;
    	struct cdev *cdev;
    	int err = -ENOMEM;
    
    	cd = __register_chrdev_region(major, baseminor, count, name);
    	if (IS_ERR(cd))
    		return PTR_ERR(cd);
    
    	cdev = cdev_alloc();
    	if (!cdev)
    		goto out2;
    
    	cdev->owner = fops->owner;
    	cdev->ops = fops;
    	kobject_set_name(&cdev->kobj, "%s", name);
    
    	err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
    	if (err)
    		goto out;
    
    	cd->cdev = cdev;
    
    	return major ? 0 : cd->major;
    out:
    	kobject_put(&cdev->kobj);
    out2:
    	kfree(__unregister_chrdev_region(cd->major, baseminor, count));
    	return err;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    3. 例子代码

    • chrdevbase.c
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define CHRDEVBASE_MAJOR 200         /* 主设备号 */
    #define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
    
    static char readbuf[100];  /* 读缓冲区 */
    static char writebuf[100]; /* 写缓冲区 */
    static char kerneldata[] = {"kernel data!"};
    
    /*
     * @description : 打开设备
     * @param – inode : 传递给驱动的 inode
     * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
     * 一般在 open 的时候将 private_data 指向设备结构体。
     * @return : 0 成功;其他 失败
     */
    static int chrdevbase_open(struct inode *inode, struct file *filp)
    {
        // printk("chrdevbase open!\r\n");
        return 0;
    }
    
    /*
     * @description : 从设备读取数据
     * @param - filp : 要打开的设备文件(文件描述符)
     * @param - buf : 返回给用户空间的数据缓冲区
     * @param - cnt : 要读取的数据长度
     * @param - offt : 相对于文件首地址的偏移
     * @return : 读取的字节数,如果为负值,表示读取失败
     */
    
    static ssize_t chrdevbase_read(struct file *filp, char __user *buf,
                                   size_t cnt, loff_t *offt)
    {
        int retvalue = 0;
    
        /* 向用户空间发送数据 */
        memcpy(readbuf, kerneldata, sizeof(kerneldata));
        retvalue = copy_to_user(buf, readbuf, cnt);
        if (retvalue == 0)
        {
            printk("kernel senddata ok!\r\n");
        }
        else
        {
            printk("kernel senddata failed!\r\n");
        }
    
        // printk("chrdevbase read!\r\n");
        return 0;
    }
    
    /*
     * @description : 向设备写数据
     * @param - filp : 设备文件,表示打开的文件描述符
     * @param - buf : 要写给设备写入的数据
     * @param - cnt : 要写入的数据长度
     * @param - offt : 相对于文件首地址的偏移
     * @return : 写入的字节数,如果为负值,表示写入失败
     */
    static ssize_t chrdevbase_write(struct file *filp,
                                    const char __user *buf,
                                    size_t cnt, loff_t *offt)
    {
        int retvalue = 0;
        /* 接收用户空间传递给内核的数据并且打印出来 */
        retvalue = copy_from_user(writebuf, buf, cnt);
        if (retvalue == 0)
        {
            printk("kernel recevdata:%s\r\n", writebuf);
        }
        else
        {
            printk("kernel recevdata failed!\r\n");
        }
    
        // printk("chrdevbase write!\r\n");
        return 0;
    }
    
    /*
     * @description : 关闭/释放设备
     * @param - filp : 要关闭的设备文件(文件描述符)
     * @return : 0 成功;其他 失败
     */
    static int chrdevbase_release(struct inode *inode,
                                  struct file *filp)
    {
        // printk("chrdevbase release!\r\n");
        return 0;
    }
    
    /*
     * 设备操作函数结构体
     */
    static struct file_operations chrdevbase_fops = {
        .owner = THIS_MODULE,
        .open = chrdevbase_open,
        .read = chrdevbase_read,
        .write = chrdevbase_write,
        .release = chrdevbase_release,
    };
    
    /*
     * @description : 驱动入口函数
     * @param : 无
     * @return : 0 成功;其他 失败
     */
    static int __init chrdevbase_init(void)
    {
        int retvalue = 0;
    
        /* 注册字符设备驱动 */
        retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME,
                                   &chrdevbase_fops);
        if (retvalue < 0)
        {
            printk("chrdevbase driver register failed\r\n");
        }
        printk("chrdevbase_init()\r\n");
        return 0;
    }
    
    /*
    * @description : 驱动出口函数
    * @param : 无
    * @return : 无
    */
    static void __exit chrdevbase_exit(void)
    {
        /* 注销字符设备驱动 */
        unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
        printk("chrdevbase_exit()\r\n");
    }
    
    /*
     * 将上面两个函数指定为驱动的入口和出口函数
     */
    module_init(chrdevbase_init);
    module_exit(chrdevbase_exit);
    
    /*
     * LICENSE 和作者信息
     */
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("chenzhiyong");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • Makefile
    obj-m += chrdevbase.o
    
    #generate the path
    CURRENT_PATH:=$(shell pwd)
    
    #the current kernel version number
    LINUX_KERNEL:=$(shell uname -r)
    
    #the absolute path
    LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
    
    #complie object
    all:
    	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
    #clean
    clean:
    	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 编译:make ,生成 *.ko 文件,它就是内核模块。
    2. 插入内核模块:insmod chrdevbase.ko
    3. 查看内核模块是否成功:dmesg | tail -n 20
    [    9.261691] rfkill: input handler disabled
    [  345.463456] kauditd_printk_skb: 32 callbacks suppressed
    [  345.463458] audit: type=1400 audit(1697963648.793:43): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="/snap/core/16202/usr/lib/snapd/snap-confine" pid=2103 comm="apparmor_parser"
    [  345.463466] audit: type=1400 audit(1697963648.793:44): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="/snap/core/16202/usr/lib/snapd/snap-confine//mount-namespace-capture-helper" pid=2103 comm="apparmor_parser"
    [  346.309668] audit: type=1400 audit(1697963649.640:45): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap.gnome-system-monitor.hook.configure" pid=2107 comm="apparmor_parser"
    [  346.777464] audit: type=1400 audit(1697963650.108:46): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap.gnome-system-monitor.gnome-system-monitor" pid=2106 comm="apparmor_parser"
    [  348.399012] audit: type=1400 audit(1697963651.727:47): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap-update-ns.gnome-system-monitor" pid=2105 comm="apparmor_parser"
    [  348.403846] audit: type=1400 audit(1697963651.731:48): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="snap-update-ns.core" pid=2109 comm="apparmor_parser"
    [  348.405261] audit: type=1400 audit(1697963651.735:49): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="snap.core.hook.configure" pid=2110 comm="apparmor_parser"
    [13529.015625] chrdevbase: loading out-of-tree module taints kernel.
    [13529.015665] chrdevbase: module verification failed: signature and/or required key missing - tainting kernel
    [13529.016265] chrdevbase driver register success
    [13529.016266] chrdevbase_init()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 查看设备列表:
      cat /proc/devices
    ......
    180 usb
    189 usb_device
    200 chrdevbase	# 设备号 200 就是我们代码中的设备号
    204 ttyMAX
    216 rfcomm
    226 drm
    238 kfd
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 创建设备,跟设备号绑定:mknod /dev/chrdevbase c 200 0

    4. 测试

    • 测试文件
    • test.c
    #include "stdio.h"
    #include "unistd.h"
    #include "sys/types.h"
    #include "sys/stat.h"
    #include "fcntl.h"
    #include "stdlib.h"
    #include "string.h"
    
    static char usrdata[] = {"usr data!"};
    
    /**
     * @brief main 函数
     *
     * @param argc
     * @param argv
     * @return int
     */
    int main(int argc, char *argv[])
    {
        int fd, retvalue;
        char *filename;
        char readbuf[100], writebuf[100];
        if (argc != 3)
        {
            printf("Error Usage!\r\n");
            return -1;
        }
        filename = argv[1];
        /* 打开驱动文件 */
        fd = open(filename, O_RDWR);
        if (fd < 0)
        {
            printf("Can't open file %s\r\n", filename);
            return -1;
        }
        if (atoi(argv[2]) == 1)
        {
            /* 从驱动文件读取数据 */
            retvalue = read(fd, readbuf, 50);
            if (retvalue < 0)
            {
                printf("read file %s failed!\r\n", filename);
            }
            else
            {
                /* 读取成功,打印出读取成功的数据 */
                printf("read data:%s\r\n", readbuf);
            }
        }
    
        if (atoi(argv[2]) == 2)
        {
            /* 向设备驱动写数据 */
            memcpy(writebuf, usrdata, sizeof(usrdata));
            retvalue = write(fd, writebuf, 50);
            if (retvalue < 0)
            {
                printf("write file %s failed!\r\n", filename);
            }
        }
        /* 关闭设备 */
        retvalue = close(fd);
        if (retvalue < 0)
        {
            printf("Can't close file %s\r\n", filename);
            return -1;
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    1. 编译:gcc -o test test.c
    2. 运行:
      从设备文件读: ./test /dev/chrdevbase 1 , 往设备文件写: /test /dev/chrdevbase 2
  • 相关阅读:
    DataFun: 微信NLP算法微服务治理
    练习 4 Web [MRCTF2020]Ez_bypass
    cobbler简介&部署
    【EMC专题】EMC测试——辐射发射测试方法
    【c代码】【字符串数组排序】
    Linux·驱动中的中断
    销售抓住客户心理的话术
    Notion AI会员订阅付费
    京东快运 | 快递单号查询API
    【Hadoop】学习笔记(二)
  • 原文地址:https://blog.csdn.net/yong1585855343/article/details/133822999