Linux字符设备驱动注册三种方法以及内核分析 |
您所在的位置:网站首页 › Linux设备号一般是动态注册还是静态注册 › Linux字符设备驱动注册三种方法以及内核分析 |
Linux驱动是用户访问底层硬件的桥梁,驱动有可以简单分成三类:字符设备、块设备、网络设备。其中最多的是字符设备,其中字符设备的注册方法主要有三种:杂项设备注册、早期字符设备注册、标准字符设备注册。以及详细介绍各类方法注册。 开发环境: PC:VMworkstation 12 运行Ubuntu12 32位虚拟机 开发板:友善之臂Tiny4412 (EXYNOS4412 Cortex-A9) Linux内核版本:Linux 3.5 PC内核阅读器:SourceInsight
一、杂项设备(misc device): 在内核路径下include\linux\miscdevice.h文件有以下内容: struct miscdevice { int minor; const char *name; const struct file_operations *fops; struct list_head list; struct device *parent; struct device *this_device; const char *nodename; umode_t mode; }; extern int misc_register(struct miscdevice * misc); extern int misc_deregister(struct miscdevice *misc); 通过上述函数的声明可知杂项设备的注册函数为:misc_register,注销函数为misc_deregister,通过sourceinsight搜索参考代码: 可知要使用一个杂项设备必须要有一个struct miscdevice 结构体,根据miscdevice.h结构体的定义和相关内核代码可知: 需要至少实现结构体内部三个参数: int minor;//次设备号(主设备号默认为10,其中杂项设备是通过早期字符设备静态添加到内核中,在早期设备说明) const char *name;//设备名称,dev下创建的设备节点名称 const struct file_operations *fops;//文件操作指针,为用户层与驱动层访问的接口 以上很明显的阐述了驱动注册之前需要的准备工作:对struct miscdevice结构体进行定义并且赋值,然而内部参数并非这么简单:struct file_operations 还嵌套一层结构体,为文件操作集合结构体。根据内核代码,我们可以找到相关所有文件操作集合的函数接口: 位于内核根目录下:include\linux下的Fs.h文件下定义了文件操作集合函数接口。通过在驱动层实现这些接口以便对驱动层的访问。通过上述分析,杂项设备注册方式很简单的暂时概括为:1、准备工作:结构体的定义与赋值、接口函数的实现。2、将结构体传入杂项设备注册函数,实现注册。3、用户层函数的编写。 讲到这里可能还不理解用户层如何通过文件操作集合定义的函数接口去实现访问内核驱动,以一个简单的代码为例: static struct file_operations fops_led= { .open=open_led, .write=write_led, .read=read_led, .release=release_led, }; static struct miscdevice misc_led= { .minor=MISC_DYNAMIC_MINOR, /*自动分配次设备号*/ .name="tiny4412_led", /*设备节点的名称*/ .fops=&fops_led /*文件操作集合*/ }; 在驱动层定义的文件操作集合为:open、write、read、release。 open:当用户层通过open(dev\设备结点)时对应驱动层实现的open函数会被调用,当用户层通过open会产生一个文件描述符,用户层再通过文件描述符去read、write操作时,会对应调用驱动层的read、write,通过这些函数的来访问驱动。相关代码: 驱动层: #include #include #include #include static int open_led (struct inode *my_inode, struct file *my_file) { printk("open_led调用成功!\n"); return 0; } static ssize_t read_led(struct file *my_file, char __user *buff, size_t cnt, loff_t *loff) { printk("read_led调用成功!\n"); return 0; } static ssize_t write_led(struct file *my_file, const char __user *buff, size_t cnt, loff_t *loff) { printk("write_led调用成功!\n"); return 0; } static int release_led(struct inode *my_inode, struct file *my_file) { printk("release_led调用成功!\n"); return 0; } static struct file_operations fops_led= { .open=open_led, .write=write_led, .read=read_led, .release=release_led, }; static struct miscdevice misc_led= { .minor=MISC_DYNAMIC_MINOR, /*自动分配次设备号*/ .name="tiny4412_led", /*设备节点的名称*/ .fops=&fops_led /*文件操作集合*/ }; static int __init tiny4412_led_init(void) { int err; err=misc_register(&misc_led); //杂项设备注册函数 if(err struct char_device_struct *cd, **cp; int ret = 0; int i;cd= kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); if (cd == NULL) return ERR_PTR(-ENOMEM); mutex_lock(&chrdevs_lock); /* temporary */ if (major == 0) { for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {/* chrdevs 存放字符设备的总个数结构体 */ if (chrdevs[i] == NULL)/* 没有被用过*/ break; } if (i == 0) {/* 满了 */ ret = -EBUSY; goto out; } major = i; ret = major; } cd->major = major;/* 装载到结构体*/ cd->baseminor = baseminor; cd->minorct = minorct; strlcpy(cd->name, name, sizeof(cd->name)); i = major_to_index(major); for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major > major || ((*cp)->major == major && (((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)))) break; /* Check for overlapping minor ranges. */ if (*cp && (*cp)->major == major) { int old_min = (*cp)->baseminor; int old_max = (*cp)->baseminor + (*cp)->minorct - 1; int new_min = baseminor; int new_max = baseminor + minorct - 1; /* New driver overlaps from the left. */ if (new_max >= old_min && new_max ret = -EBUSY; goto out; } } cd->next = *cp; *cp = cd; mutex_unlock(&chrdevs_lock); return cd; out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); 这个函数有点长,其实主要功能就是以下部分: 如果传入的major为0,则经过一个for循环遍历所有的chrdevs结构体数组: 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]; 每一位成员为一个设备的节点信息,组成一个链表通过这个for循环遍历这个链表来找到没有使用的主设备号,那很明显,当传入进来的major为0,会自动分配主设备号,这个major也就是register_chrdev的第一个参数。并且当没找到时会报错。接着这个函数将主设备号装载在struct char_device_struct结构体内部进行返回,继续在__register_chrdev函数里面操作: 再次判断得到的结构体,并且再次封装结构体,通过cdev_add添加到内核。通过上面的分析,register_chrdev主要是用于生成一个主设备号,由杂项设备可知杂项设备的主设备号为10,那是否也是通过register_chrdev函数注册的主设备号,在misc.c文件里面我们发现了一个杂项设备初始化的函数:misc_init其函数体为: static int __init misc_init(void) { int err; #ifdef CONFIG_PROC_FS proc_create("misc", 0, NULL, &misc_proc_fops);/* 交互式文件系统 */ #endif misc_class = class_create(THIS_MODULE, "misc");/* 创建一个设备类 */ err = PTR_ERR(misc_class); if (IS_ERR(misc_class)) goto fail_remove; err = -EIO; if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))/* 注册字符设备 在/sys/class/创建子目录*/ goto fail_printk; misc_class->devnode = misc_devnode; return 0; fail_printk: printk("unable to get major %d for misc devices\n", MISC_MAJOR); class_destroy(misc_class); fail_remove: remove_proc_entry("misc", NULL); return err; } 很显然,里面调用了register_chrdev在major.h文件夹里面定义#define MISC_MAJOR 10,这就是杂项设备的主设备号为10的原因了,在前面我们介绍过早期字符设备register_chrdev注册他并没有像杂项设备一样调用device_create来产生一个设备节点,所以,如何使用这个函数产生的主设备号呢? 这个问题想要达成的效果就是在/dev/下生成设备节点,刚好在linux下有一个创建设备结点的命令:mknod,用法: mknod Name {b|c} major minor :Name设备名称 b|c块设备还是字符设备 major主设备号 minor次设备号 那么,问题又来了设备号是个坑,但是我们可以通过打印函数打印创建的主设备号,然后通过mknod创建节点,测试代码如下: #include #include #include #include #include #include #include #include static int open_led (struct inode *my_inode, struct file *my_file) { printk("open_led调用成功!\n"); return 0; } static ssize_t read_led(struct file *my_file, char __user *buff, size_t cnt, loff_t *loff) { printk("read_led调用成功!\n"); return cnt; } static ssize_t write_led(struct file *my_file, const char __user *buff, size_t cnt, loff_t *loff) { printk("write_led调用成功!\n"); return cnt; } static int release_led(struct inode *my_inode, struct file *my_file) { printk("release_led调用成功!\n"); return 0; } static struct file_operations fops_led= { .open=open_led, .write=write_led, .read=read_led, .release=release_led, }; static unsigned int major; static int __init tiny4412_led_init(void) /*insmod xxx.ko*/ { major=register_chrdev(0,"led", &fops_led); printk("major:%d\n",major); printk("提示:驱动安装成功!\n"); } static void __exit tiny4412_led_exit(void) { unregister_chrdev(major,"led"); printk("提示: 驱动卸载成功!\n"); } module_init(tiny4412_led_init); /*指定驱动的入口函数*/ module_exit(tiny4412_led_exit); /*指定驱动的出口函数*/ MODULE_LICENSE("GPL"); /*指定驱动许可证*/将此生成.ko文件上传到开发板insmod安装驱动后出行: 提示生成的主设备号为250,现在我们用mknod生成两个设备节点:
生成的两个设备节点,现在通过用户层去访问驱动,用户层代码为: #include #include #include #include int main(int argc ,char ** argv) { int i=0,count,err; if(argc!=2) { printf("usage: ./app \n"); return 0; } err=open(argv[1],2); if(err |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |