10.1设置设备节点

    xiaoxiao2025-02-12  16

    1.Module

    模块(module)是一种向Linux内核添加设备驱动程序、文件系统及其他组件的有效方法,而无需连编新内核或重启系统,模块消除了宏内核的许多限制,模块有许多优点

    1. 通过使用模块,内核发布者能够预先编译大量驱动程序,但并不会造成内核镜像发生膨胀,在自动检测硬件(例如USB)或用户提示之后,安装例程选择适当的模块并将其添加到内核中

    2. 内核开发者可以将试验性的代码打包到模块中,模块可以卸载、修改代码或重新打包后可以重新加载,这使得可以快速测试新特性,无需每次都重启系统

    需要包含以下头文件:<linux/kernel.h> <linux/module.h>

    写驱动时需要使用module_init和 module_exit和 MODULE_LICENSE("GPL");

    2.设备号

          一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设

    备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各

    设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么

    ,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号

    分别为1和2。这里,次设备号就分别表示两个LED灯

    查看系统已经分配的主设备有哪些:

    #cat /proc/devices 

    (1)怎么注册设备号

    内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。这三个函数都会调用一个共用的 __register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。

    (2)三个方法的区别

    参考https://blog.csdn.net/Tommy_wxie/article/details/7195471

    extern int register_chrdev_region(dev_t, unsigned, const char *); //静态的申请和注册设备号

    extern int  alloc_chrdev_region(dev_t, unsigned, const char *);     //动态的申请注册一个设备号

    extern int register_chrdev(unsigned int, const char *,struct file_operations *);//int为0时候动态注册,非零时候静态注册。

    register_chrdev_region()是register_chrdev()的升级版本。

    但是常用alloc_chrdev_region。

    register_chrdev_region() 函数用于分配指定的设备编号范围。如果申请的设备编号范围跨越了主设备号,它会把分配范围内的编号按主设备号分割成较小的子范围,并在每个子范围上调用 __register_chrdev_region() 。如果其中有一次分配失败的话,那会把之前成功分配的都全部退回。

    register_chrdev() 是一个老式分配设备编号范围的函数。它分配一个单独主设备号和 0 ~ 255 的次设备号范围。如果申请的主设备号为 0 则动态分配一个。该函数还需传入一个 file_operations 结构的指针,函数内部自动分配了一个新的 cdev 结构。

    alloc_chrdev_region() 函数用于动态申请设备编号范围,这个函数好像并没有检查范围过大的情况,不过动态分配总是找个空的散列桶,所以问题也不大。通过指针参数返回实际获得的起始设备编号。

     

    3.设置设备节点

    设备文件一般称为设备节点,如执行#cat /proc/devices后的/dev/xxx文件

    由于操作节点之前kernel就需要有主设备号的信息,因此主设备号的申请、具有该主设备号的字符设备的添加都需要在驱动模块的初始化函数中执行

     

    udev:

    udev 是Linux kernel 2.6系列的设备管理器。它主要的功能是管理/dev目录底下的设备节点,根据/sys/class的设备信息自动创建设备节点。

    为什么udevhui根据/sys/class的设备信息自动创建设备节点?

    因为我们在写脚本程序/etc/init.d/rcS时设置为:

    它是用来接替devfs及热插拔的功能,这意味着它要在添加/删除硬件时处理/dev目录以及所有用户空间的行为。

     

    设置设备节点有两种方法:手动和自动

    (1)手动用mknod创建设备节点

    在开发版的终端上用 mknod /dev/xxx c 主设备号  次设备号

    (2)利用udev自动创建设备节点

    在加载驱动模块后,就要自己使用mknod创建设备节点,这样虽然是可行的,但是比较麻烦。我们可以在__init()函数里面添加一些函数,自动创建设备节点。创建设备节点使用了两个函数 class_create()和device_create(),当然在__exit()函数里,要使用class_destory()和device_desotry()注销创建的设备节点。

     

    在驱动用加入对udev的支持主要做的就是:在驱动初始化的代码里调用class_create(...)为该设备创建一个class,再为每个设备调用device_create(...)

     

    内核中定义的struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。

     

    在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用 device_create创建对应的设备。

    加载模块后,会自动在/dev/下创建myled设备文件。

     

    linux驱动写完之后编译完成,会生成.ko文件,此时执行insmod指令加载完模块后,可在cat /proc/devices看到设备名和设备号。

     

    4.程序完善

    (1)定义类

    (2)自动创建设备节点

    这样当我们insmod时,内核就会自动把帮我们生成/dev/myled节点。

    5.完整代码

    #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; static struct class *firstdrv_led_class; static struct class_device *firstdrv_led_class_dev; static int firstdrv_led_open(struct inode *inode, struct file *file) {     printk("second_open\n");     return 0; } static ssize_t firstdrv_led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) {     printk("second_write\n");     return 0; } /* 这个结构是字符设备驱动程序的核心 * 当应用程序操作设备文件时所调用的open、read、write等函数, * 最终会调用这个结构中指定的对应函数 */ static struct file_operations firstdrv_led_fops = {     .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */     .open   =   firstdrv_led_open,                .write    =    firstdrv_led_write,        }; int major; int firstdrv_led_init(void) {     major =register_chrdev(0,"myled",&firstdrv_led_fops);//注册firstdrv,告诉内核,注冊设备时给设备号写0,则内核会自己主动分配一个主设备号返回。     firstdrv_led_class = class_create(THIS_MODULE,"firstdrv_led");     firstdrv_led_class_dev = class_device_create(firstdrv_led_class,NULL,MKDEV(major,0),NULL,"myled");     return 0; } void firstdrv_led_exit(void) {     unregister_chrdev(major,"firstdrv_led");//卸载     class_device_unregister(firstdrv_led_class_dev);     class_destroy(firstdrv_led_class); } module_init(firstdrv_led_init); module_exit(firstdrv_led_exit); MODULE_LICENSE("GPL");

    6.测试

    内核直接帮我们生成了设备节点,主次设备号为252 0

    最新回复(0)