这两个函数是与字符设备初始化相关的内核函数。要理解这两个函数,就必须知道字符设备的结构和创建字符设备的过程。字符设备请参考以下文章《手把手教Linux驱动3-之字符设备架构详解,有这篇就够了》1.字符设备架构下面我们以两个设备:LED和MPU6050为例来说明字符设备的结构。两个器件,它们通过外围电路连接到SOC的相应引脚上。要操作外设,程序必须通过在soc中设置相应的SFR来与外设进行交互。2、驱动层中的每个字符设备都要先定义一个结构体变量structcdev,并注册到内核中。所有变量将由内核中的链表进程管理,成员列表用于将所有链表串联起来。操作外设的功能函数都封装在structfile_operations中,包括read、write等。每个字符设备都必须有一个设备号,存放在成员dev中,主次设备号只能分配一次给所有字符设备号都由数组chrdevs管理。chrdevs是一个指针数组。成员类型为**structchar_device_struct***。下标与字符设备号有一定的对应关系。**structchar_device_struct**有一个成员:unsignedintmajor;structcdev*cdev;major:是主设备号cdev:指向字符设备号3对应的cdev结构体,如果应用层和VFS层的用户要对硬件进行操作,必须调用structfile_operations在内核中,那么如何才能找到这个结构呢?您必须依靠文件节点才能找到它。可以使用如下命令创建mknod/dev/ledc2500mknod创建设备文件,可以是字符设备也可以是块设备/dev/led设备文件名c字符设备250主设备号0次设备号最重要characterdevicefileattributes中的属性为字符设备号,与chedevs的下标有一定的对应关系对于通过mknod创建的文件,VFS层会分配一个结构体变量来维护File,类型为structinode每次new文件创建后,内核会创建一个不同的结构变量与之对应。要操作字符设备,必须通过系统调用open()打开字符设备。该函数将返回一个唯一的整数文件描述符。同时在内核中分配了一个结构体变量,类型为structfile,与文件描述符一一对应。该结构在structtask_struct中维护。每次打开一个文件,一个不同的文件描述符,所以需要用不同的变量来保存文件描述符2.字符设备的创建过程了解了架构之后,我们来看一下创建字符设备的完整过程内核与对应的函数调用关系:如下图所示,字符设备的创建主要包括以下三个步骤:申请设备号初始化cdev注册cdev调用的函数见下右include#include#include#include#includestaticintmajor=237;staticintminor=0;staticdev_tdevno;staticstructcdevcdev;staticinthello_open(structinode*inode,structfile*filep){printk("hello_open()\n");return0;}staticstructfile_operationshello_ops={.open=hello_open,};staticinthello_init(void){intresult;intererror;printk("hello_init\n");devno=MKDEV(major,minor);result=register_chrdev_region(devno,1,"test");if(result<0){printk("register_chrdev_regionfail\n");returnresult;}cdev_init(&cdev,&hello_ops);error=cdev_add(&cdev,devno,1);if(error<0){printk("cdev_addfail\n");unregister_chrdev_region(devno,1);returnerror;}return0;}staticvoidhello_exit(void){printk("hello_exit\n");cdev_del(cdev);unregister_chrdev_region(devno,1);return;}module_init(hello_init);module_exit(hello_exit);本示例代码的主要功能:申请字符设备号237,初始化cdev,注册cdev应用。如果要使用,还需要创建一个字符设备节点mknod/dev/testc2370,以便应用程序通过设备节点/dev/test调用相应的内核操作函数。open=hello_open,/**一口Linux*2021.6.21*version:1.0.0*/#include#include#include#includemain(){intfd;fd=open("/dev/test",O_RDWR);if(fd<0){perror("openfail\n");return;}printf("openok\n");}3.函数及定义理解了上面的字符设备创建步骤后,我们就可以真正分析cdev_init和cdev_alloc这两个函数了1.cdev_init()voidcdev_init(structcdev*cdev,conststructfile_operations*fops)函数的原型用于初始化cdev结构体,填充其成员ops参数cdev:字符设备fops:驱动操作函数集的返回值为nil。该函数的实现如下:/***cdev_init()-initializeacdevstructure*@cdev:thestructuretoinitialize*@fops:thefile_operationsforthisdevice**Initializes@cdev,remembering@fops,makingitreadytoaddtothe*systemwithcdev_add().*/voidcdev_init(structcdev*cdev,conststructfile_operations*fops){memset(cdev,0,sizeof*cdev);INIT_LIST_HEAD(&cdev->list);kobject_init(&cdev->kobj,&ktype_cdev_default);cdev->ops=fops;}2.使用cdev_alloc原型structcdev*cdev_alloc(void)函数分配cdev结构体并加入内核参数返回值成功:返回分配的cdev结构体变量指针失败:返回NULL该函数实现如下:/***cdev_alloc()-allocateacdevstructure**分配并返回acdevstructure,orNULLonfailure.*/structcdev*cdev_alloc(void){structcdev*p=kzalloc(sizeof(structcdev),GFP_KERNEL);if(p){INIT_LIST_HEAD(&p->list);kobject_init(&p->kobj,&ktype_cdev_dynamic);}returnp;}注意,函数分配的cdev需要释放。该函数不初始化cdev->ops成员。4.cdev_alloc()的使用这个函数主要是为了省去用户对cdev的操作。它只需要提供**structfile_operations**变量传递给下面的函数注册字符设备staticinlineintregister_chrdev(unsignedintmajor,constchar*name,conststructfile_operations*fops){return__register_chrdev(major,0,256,name,fops);}其中函数__register_chrdev()定义如下:/***__register_chrdev()-createandregisteracdevoccuying*@arangeofminorsmajor:majordevicenumberor0fordynamicallocation*@baseminor:firstofther请求的次要编号范围*@count:需要的次要编号数量*@name:设备范围的名称*@fops:与此设备相关的文件操作**如果@major==0此函数将动态分配amajor并返回*它的编号。**如果@major>0此函数将尝试使用给定的*主要编号保留设备并将返回zeroonsuccess。**返回sa-veerrnoonthevfailtoofthisdevicewith*nameofthedevice/**Thename它只帮助跟踪不同的设备所有者。如果*你的模块名称只有一种类型的设备,它可以看到。例如模块的名称*在这里。major,baseminor,count,name);if(IS_ERR(cd))returnPTR_ERR(cd);cdev=cdev_alloc();if(!cdev)gotoout2;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)gotoout;cd->cdev=cdev;returnmajor?0:cd->major;out:kobject_put(&cdev->kobj);out2:kfree(__unregister_chrdev_region(cd->major,baseminor,count));returnerr;}可以看到这个函数重用了cdev_alloc()和cdev_add()。我们只需要提供以下三个参数:unsignedintmajor主设备号constchar*name设备号nameconststructfile_operations*fops驱动操作函数集合5.结论cdev_alloc()函数等同于structcdevcdev;cdev_init($cdev,&hello_ops)