为了加深对主次设备号以及设备文件系统的理解,我们编写一个最基础的字符设备文件系统,使用register_chrdev函数注册,手动指定驱动的主设备号,然后通过命令行创建设备节点,最后编写测试程序验证驱动的正确性。 参考驱动源码如下: - #include <linux/module.h>
- #include <asm/uaccess.h>
- #include <linux/cdev.h>
- #include <linux/fs.h>
- #define x4412_char_MAJOR 224 //主设备号
- static unsigned char simple_inc=0;
- static unsigned char demoBuffer[256];
- int x4412_char_open(struct inode *inode, struct file *filp)
- {
- if(simple_inc>0)
- return -ERESTARTSYS;
- simple_inc++;
- return 0;
- }
- int x4412_char_release(struct inode *inode, struct file *filp)
- {
- simple_inc--;
- return 0;
- }
- ssize_t x4412_char_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
- {
- /* 把数据复制到应用程序空间 */
- if (copy_to_user(buf,demoBuffer,count))
- {
- count=-EFAULT;
- }
- return count;
- }
- ssize_t x4412_char_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
- {
- /* 把数据复制到内核空间 */
- if (copy_from_user(demoBuffer+*f_pos, buf, count))
- {
- count = -EFAULT;
- }
- return count;
- }
- struct file_operations x4412_char_fops = {
- .owner = THIS_MODULE,
- .read = x4412_char_read,
- .write = x4412_char_write,
- .open = x4412_char_open,
- .release = x4412_char_release,
- };
- int x4412_char_init_module(void)
- {
- int ret;
- printk("x4412_char_init_module!\n");
- //根据设备号与设备名注册字符设备
- ret = register_chrdev(x4412_char_MAJOR, "x4412_char", &x4412_char_fops);
- if (ret < 0)
- {
- printk("Unable to register character device %d!\n",x4412_char_MAJOR);
- return ret;
- }
- return 0;
- }
- void x4412_char_cleanup_module(void)
- {
- unregister_chrdev(x4412_char_MAJOR, "x4412_char");
- printk("x4412_char_cleanup_module!\n");
- }
-
- module_init(x4412_char_init_module);
- module_exit(x4412_char_cleanup_module);
复制代码 本程序在模块初始化函数中调用register_chrdev函数,向内核注册一个名为x4412_char,主设备号为224的字符设备。在file_operations结构体中指定了x4412_char_read和x4412_char_write两个回调函数,他们分别使用copy_to_user和copy_from_user函数和应用程序交互数据。 将源码文件命名为x4412-char.c,并放到kernel/drivers/char/x4412目录,在kernel/drivers/char/Makefile文件中添加如下语句: - obj-$(CONFIG_X4412_CHAR_DRIVER) += x4412-char.o
复制代码 在kernel/drivers/char/Kconfig文件中添加如下语句: - config X4412_CHAR_DRIVER
- tristate "x4412 char driver"
- default y
- help
- compile for x4412 char driver,y for kernel,m for module.
复制代码 为了一并测试注销设备的函数unregister_chrdev,将驱动编译成模块。加载驱动后,可以在/proc/modules下找到注册的设备名称和主设备号: - [root@x4412 mnt]# more /proc/devices |grep x4412_char
- [root@x4412 mnt]# insmod x4412-char.ko
- [ 168.241683] x4412_char_init_module!
- [root@x4412 mnt]# more /proc/devices |grep x4412_char
- 224 x4412_char
- [root@x4412 mnt]#
复制代码 在驱动中并没有创建设备节点和次设备号。通过mknod指令手动创建设备结点以及次设备号: - [root@x4412 mnt]# mknod /dev/x4412-char c 224 0
- [root@x4412 mnt]# ls /dev/x4412-char -la
- crw-r--r-- 1 root root 224, 0 Sep 27 08:48 /dev/x4412-char
复制代码- #include <stdio.h>
- #include <stdlib.h>
- #include <fcntl.h>
- void main(void)
- {
- int fd;
- int i;
- char data[256];
- int retval;
- fd=open("/dev/x4412-char",O_RDWR);
- if(fd==-1)
- {
- perror("error open\n");
- exit(-1);
- }
- printf("open /dev/x4412-char successful\n");
- //写数据
- retval=write(fd,"www.9tripod.com",15);
- if(retval==-1)
- {
- perror("write error\n");
- exit(-1);
- }
- //读数据
- retval=read(fd,data,15);
- if(retval==-1)
- {
- perror("read error\n");
- exit(-1);
- }
- data[retval]=0;
- printf("read data:\n");
- printf("%s\n",data);
- //关闭设备
- close(fd);
- }
复制代码 将应用程序源码命名为x4412-char-app.c,保存到ubuntu的samba目录,执行如下指令编译测试程序: - arm-none-linux-gnueabi-gcc x4412-char-app.c -o x4412-char-app
复制代码 将生成的测试程序映像x4412-char-app拷贝到开发板并执行,可以看到现象如下: - [root@x4412 mnt]# ./x4412-char-app
- open /dev/x4412-char successful
- read data:
- www.9tripod.com
复制代码 再从头梳理一遍整个实验原理。使用register_chrdev函数注册设备时,主设备号是手动指定的。如果内核已经存在这个主设备号,将不能正常注册。register_chrdev函数引入了自动注册主设备号的功能,当第一个参数为0时表示自动注册主设备号。不仿将驱动中的x4412_char_MAJOR定义为0,再来观察结果: - [root@x4412 mnt]# insmod x4412-char.ko
- [ 98.050237] x4412_char_init_module!
- [root@x4412 mnt]# more /proc/devices |grep x4412_char
- 249 x4412_char
- [root@x4412 mnt]#
复制代码 可见,register_chrdev函数自动分配主设备号为249。使用自动分配的方式可以消除设备注册失败的风险。这时创立设备节点时,应该对应正确的设备号了: - [root@x4412 mnt]# mknod /dev/x4412-char c 249 0
- [root@x4412 mnt]# ./x4412-char-app
- open /dev/x4412-char successful
- read data:
- www.9tripod.com
复制代码 使用rmmod指令卸载驱动模块: - [root@x4412 mnt]# rmmod x4412-char.ko
- [ 382.105242] x4412_char_cleanup_module!
复制代码 上面打印语句是在驱动的卸载函数中调用的。尽管这时模块被卸载,但是在/dev下的设备节点以及在/proc/devices下的设备名称仍然没有被删掉,机器重启之后,/dev一睥设备节点也不会删掉,这就是devfs文件系统最大的病源,udev文件系统应声而出,解决了devfs一系列令系统工程师头痛的问题。 可以通过class_create和device_create函数自动创建设备节点,在驱动源码中声明头文件: - #include <linux/device.h>
复制代码 在模块初始化函数中添加相应函数自动创建设备节点: - int x4412_char_init_module(void)
- {
- int ret;
- printk("x4412_char_init_module!\n");
- //根据设备号与设备名注册字符设备
- ret = register_chrdev(x4412_char_MAJOR, "x4412_char", &x4412_char_fops);
- if (ret < 0)
- {
- printk("Unable to register character device %d!\n",x4412_char_MAJOR);
- return ret;
- }
- //创建class
- x4412_char_dev_class = class_create(THIS_MODULE, "x4412_char");
- if (IS_ERR(x4412_char_dev_class))
- {
- printk("create x4412_char dev class error!\r\n");
- unregister_chrdev(x4412_char_MAJOR, "x4412_char");
- return PTR_ERR(x4412_char_dev_class);
- }
- //创建节点
- device_create(x4412_char_dev_class, NULL, MKDEV(x4412_char_MAJOR, 0), NULL, "x4412-char");
- return 0;
- }
复制代码 在模块卸载函数中添加相应的卸载函数: - void x4412_char_cleanup_module(void)
- {
- unregister_chrdev(x4412_char_MAJOR, "x4412_char");
- device_destroy(x4412_char_dev_class, MKDEV(x4412_char_MAJOR, 0));
- class_destroy(x4412_char_dev_class);
- printk("x4412_char cleanup success!\n");
- }
复制代码 指定主设备号: - #define x4412_char_MAJOR 224
复制代码 将模块编译进内核,不要编译成模块形式,更新内核,观察设备节点是否自动生成,并用测试程序测试驱动是否正常。 - [root@x4412 mnt]# ls /dev/x4412-char -la
- crw-rw---- 1 root root 224, 0 Sep 27 10:24 /dev/x4412-char
- [root@x4412 mnt]# more /proc/devices |grep x4412_char
- 224 x4412_char
- [root@x4412 mnt]# ./x4412-char-app
- open /dev/x4412-char successful
- read data:
- www.9tripod.com
复制代码 使用这种自动生成节点的方法,如果将主设备号定义为0,即自动生成主设备号,将无法自动产生设备节点。即便是手动指定主设备号,将驱动编译成模块后,通过insmod手动加载时,也不会产生设备节点。可见,devfs文件系统确实存在相当多的毛病。
|