• Ubuntu18.04添加内核模块(字符设备)


    Ubuntu18.04添加内核模块(字符设备)

    虚拟机Ubuntu18.04(内核版本linux-5.4.0-135-generic)

    参考

    嵌入式Linux驱动开发(一)——字符设备驱动框架入门

    1 编译内核模块

    • 创建字符设备代码文件char_dev.c
    #include      //定义了module_init
    #include    //最基本的头文件,其中定义了MODULE_LICENSE这一类宏
    #include        // file_operations结构体定义在该头文件中
    #include     //class、class_device结构体的定义位置
    
    static const char* devive_name = "first_driver";  //  定义设备名
    static struct class *first_class;    //定义class结构体
    static struct device *first_dev;    //定义device结构体
    
    //定义了open函数
    static int first_drv_open (struct inode *inode, struct file *file)
    {
            printk("open\n");
            return 0;
    }
    
    //定义了write函数
    static ssize_t first_drv_write (struct file *file, const char __user *buf, size_t size, loff_t * ppos)
    {
            printk("write\n");
            return 0;
    }
    
    //在file_operations中注册open和write函数
    static struct file_operations first_drv_fo =
    {
            .owner  =  THIS_MODULE,
    
            //将对应的函数关联在file_operations的结构体中
            .open   =  first_drv_open,      
            .write  =  first_drv_write,
    };
    
    static int dev_id = 0;     //初始化的设备号0
    //init驱动的入口函数
    static int __init first_drv_init(void)
    {      
            //注册设备,实际是将file_operations结构体放到内核的制定数组中,以便管理
            //在register_chrdev中制定dev_id作为主设备号,若dev_id为0则自动分配一个主设备号
            dev_id = register_chrdev(dev_id, devive_name , &first_drv_fo);
    
    	    first_class = class_create(THIS_MODULE, "first_drv");    //初始化class结构体,指定设备文件名
    
        	first_dev = device_create(first_class, NULL, MKDEV(dev_id, 0), NULL, "first_drv");// 根据class来初始化device,会创建出对应的设备文件 /dev/first_drv
         
            printk("init\n");
            return 0;
    }
    
    //驱动的出口函数
    static void __exit first_drv_exit(void)
    {
            printk("exit\n");
            unregister_chrdev(dev_id, devive_name);  //卸载设备,实际是将file_operations结构体从内核维护的相关数组中以主设备号作为索引删除
    	    device_unregister(first_dev); // 后创建的先卸载
        	class_destroy(first_class);
    }
    
    //内核将通过这个宏,来直到这个驱动的入口和出口函数
    module_init(first_drv_init);  
    module_exit(first_drv_exit);
    
    MODULE_AUTHOR("Ethan Lee <4128127@qq.com>");
    MODULE_LICENSE("GPL");  //指定协议
    
    • 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
    • 同目录下创建Makefile文件:
    obj-m += char_dev.o
    KERN_DIR=/usr/src/linux-headers-5.4.0-135-generic
    
    all:
    	make -C ${KERN_DIR} M=${shell pwd} modules
    
    clean:
    	rm -f *.ko *.o *.mod.o *.mod.c *.sysvers
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 编译:
    make
    
    • 1

    2 加载内核模块

    sudo insmod char_dev.ko
    
    • 1
    • 查看是否添加成功:
    cat /proc/devices
    
    • 1

    结果如下:

    Character devices:
     ...
     189 usb_device
     204 ttyMAX
     226 drm
     240 first_driver  #这里是我们添加的模块
     241 aux
     242 hidraw
    ...
    
    Block devices:
      7 loop
      8 sd
      9 md
     11 sr
     65 sd
     66 sd
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 创建一个测试程序char_dev_test.c
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char **argv)
    {
        int fd;      //声明设备描述符
        int val = 1;  //随便定义变量传入到
        fd = open("/dev/first_drv",  O_RDWR);  //根据设备描述符打开设备
        if(fd < 0)          //打开失败
                printf("can't open\n");  
        write(fd, &val, 4);  //根据文件描述符调用write
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 编译并运行测试程序:
    gcc char_dev_test.c -o char_dev_test
    
    sudo ./char_dev_test
    
    • 1
    • 2
    • 3
    • 查看结果:
    $ dmesg  | tail -10
    [ 1746.094412] CPU3 has been hot-added
    [ 1746.094945] CPU4 has been hot-added
    [ 1746.097525] CPU5 has been hot-added
    [ 1746.098038] CPU6 has been hot-added
    [ 1746.098708] CPU7 has been hot-added
    [ 2861.264107] char_dev: loading out-of-tree module taints kernel.
    [ 2861.264142] char_dev: module verification failed: signature and/or required key missing - tainting kernel
    [ 2861.264398] init
    [ 3070.234439] open
    [ 3070.234441] write
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.卸载内核模块

    sudo rmmod char_dev
    
    • 1
    • 查看结果:
    $ dmesg  | tail -1
    [ 4282.264114] exit
    
    • 1
    • 2
  • 相关阅读:
    行为型设计模式(上)
    uni-app canvas创建画布
    Spring Data JPA 之 @Entity 回调方法
    wpf devexpress 创建布局
    一文讲透 RocketMQ 消费者是如何负载均衡的
    解锁前端Vue3宝藏级资料 第三章 Vue Router路由器的使用
    Linux 内核中根据文件inode号获取其对应的struct inode
    STM32F407ZGT6移植HC-05蓝牙模块
    Dubbo的注解配置和XML配置
    PMD 检查java代码:方法和类的圈复杂度(CyclomaticComplexity )
  • 原文地址:https://blog.csdn.net/weixin_43739110/article/details/136498610