在前面的devfs文件系统的实验中,是使用register_chrdev函数注册的字符设备驱动。本章节通过cdev_init及cdev_add函数注册一个字符设备驱动,同时通过put_user和get_user函数实现内核和应用程序之间的简单数据交互。 在kernel/drivers/char/x4412目录下新建x4412-cdev.c文件,编辑内容如下: - #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/errno.h>
- #include <linux/init.h>
- #include <linux/cdev.h>
- #include <asm/uaccess.h>
- #include <linux/device.h>
- static int x4412_cdev_major;
- static struct cdev x4412_cdev;
- static struct class *cdev_class;
- int x4412_cdev_open(struct inode *inode, struct file *filp)
- {
- return 0;
- }
- int x4412_cdev_release(struct inode *inode, struct file *filp)
- {
- return 0;
- }
- static long x4412_cdev_ioctl(struct file *file,unsigned int cmd, unsigned long arg)
- {
- return 0;
- }
- static ssize_t x4412_cdev_read(struct file *filp, char __user *buf,size_t size,loff_t *ppos)
- {
- int read_sizes = 0;
- char *p;
- char Message[]="This is x4412/ibox devboard.";
-
- p = Message;
- while(size && *p)
- {
- if(put_user(*(p++),buf++))
- return -EINVAL;
- size--;
- read_sizes++;
- }
- return read_sizes;
- }
- static ssize_t x4412_cdev_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)
- {
- int i;
- char str;
- for(i=0;i<size;i++)
- {
- if(!get_user(str,buf++))
- printk("%c",str);
- }
- printk("\r\n");
- return size;
- }
- static const struct file_operations x4412_fops =
- {
- .owner = THIS_MODULE,
- .read = x4412_cdev_read,
- .write = x4412_cdev_write,
- .unlocked_ioctl = x4412_cdev_ioctl,
- .open = x4412_cdev_open,
- .release = x4412_cdev_release,
- };
- static void x4412_setup_cdev(void)
- {
- int err, devno = MKDEV(x4412_cdev_major, 0);
- cdev_init(&x4412_cdev, &x4412_fops);
- err = cdev_add(&x4412_cdev, devno, 1);
- if (err)
- printk(KERN_NOTICE "Error %d adding x4412_cdev", err);
- }
-
- static void x4412_clear_cdev(void)
- {
- cdev_del(&x4412_cdev);
- unregister_chrdev_region(MKDEV(x4412_cdev_major, 0), 1);
- }
- int x4412_cdev_init(void)
- {
- int result;
- dev_t devno;
- result = alloc_chrdev_region(&devno, 0, 1, "x4412-cdev");
- x4412_cdev_major = MAJOR(devno);
- if(result < 0)
- {
- printk("register x4412-cdev error!\r\n");
- return result;
- }
- printk("x4412-cdev main dev number:%d\r\n",x4412_cdev_major);
- x4412_setup_cdev();
- cdev_class = class_create(THIS_MODULE,"x4412-cdev");
- if(IS_ERR(cdev_class))
- {
- x4412_clear_cdev();
- return PTR_ERR(cdev_class);
- }
- device_create(cdev_class,NULL,MKDEV(x4412_cdev_major,0),NULL,"x4412-cdev");
- return 0;
- }
- void x4412_cdev_exit(void)
- {
- device_destroy(cdev_class,MKDEV(x4412_cdev_major,0));
- class_destroy(cdev_class);
- x4412_clear_cdev();
- }
- MODULE_AUTHOR("www.9tripod.com");
- MODULE_LICENSE("GPL");
- module_init(x4412_cdev_init);
- module_exit(x4412_cdev_exit);
复制代码 在模块初始化函数x4412_cdev_init中,通过alloc_chrdev_region函数自动分配设备号,再通过MAJOR函数将主设备号保存到全局变量x4412_cdev_major中,供其他函数调用。紧接着在x4412_setup_cdev函数中,通过MKDEV函数获取设备号,cdev_init和cdev_add函数实现file_operations相关函数的绑定和字符设备驱动的注册。class_create和device_create函数用于创建设备节点。 驱动的框架搭建完成后,开始给file_operations成员封装必要的函数。本实验需要通过get_user和put_user函数实现内核和应用程序的数据交互,可以通过read和write函数实现。read函数对应x4412_cdev_read,函数中定义了一个字符串数组Message,同时定义了一个指针指向该字符串,然后通过put_user函数将这个字符串传送给上层应用。write函数对应x4412_cdev_write,函数中定义了一个字符变量str,通过get_user函数从上层应用读取一串字符并打印出来。file_operations结构体的其他几个成员都直接返回0,不做任何操作。 在模块卸载函数中,device_destroy和class_destroy函数用于卸载设备节点和/sysfs目录下生成的相关目录,cdev_del函数用于注销设备驱动,unregister_chrdev_region函数用于注销设备号。注意,这几个函数在卸载函数中的先后顺序千万不要颠倒,否则将会崩溃。 在kernel/drivers/char/x4412/Kconfig中添加如下语句: - config X4412_CDEV_DRIVER
- tristate "x4412 cdev driver"
- default m
- help
- compile for x4412 cdev driver,y for kernel,m for module.
复制代码 在kernel/drivers/char/x4412/Makefile中添加如下语句: - obj-$(CONFIG_X4412_CDEV_DRIVER) += x4412-cdev.o
复制代码 编译内核,在kernel/drivers/char/x4412目录下将会生成驱动模块x4412-cdev.ko文件。 在ubuntu的用户目录或samba目录下新建应用程序x4412-cdev-app.c,编辑内容如下: - #include <stdio.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #define DEVICE_NAME "/dev/x4412-cdev"
-
- int main(int argc,char **argv)
- {
- int fd;
- char buf[30];
- char str[]="hello,x4412!";
-
- fd = open(DEVICE_NAME,O_RDWR);
- if(fd == -1)
- {
- printf("open device %s error \n",DEVICE_NAME);
- }
- else
- {
- read(fd,buf,30);
- printf("read from kernel: %s\r\n",buf);
- usleep(5000);
- printf("write value to kernel:\r\n");
- usleep(5000);
- write(fd,str,12);
- close(fd);
- }
- return 0;
- }
复制代码 应用程序通过open函数打开驱动后,调用read函数从驱动中读取数据并打印出来,然后将字符串数组str的内容通过write函数写到驱动中,在驱动中将应用写入的字符串打印出来。 执行如下指令编译应用程序: - arm-none-linux-gnueabi-gcc x4412-cdev-app.c -o x4412-cdev-app
复制代码 将映像文件x4412-cdev.ko及x4412-cdev-app拷贝到开发板,加载驱动测试: - [root@x4412 mnt]# cd /sys/devices/virtual/
- [root@x4412 virtual]# ls
- android_usb/ input/ net/ sound/ vc/
- bdi/ mali/ ppp/ switch/ video4linux/
- block/ mem/ rc/ tty/ vtconsole/
- graphics/ misc/ regulator/ ump/
- [root@x4412 virtual]# insmod /mnt/x4412-cdev.ko
- [ 5602.304684] x4412-cdev main dev number:249
- [root@x4412 virtual]# ls
- android_usb/ input/ net/ sound/ vc/
- bdi/ mali/ ppp/ switch/ video4linux/
- block/ mem/ rc/ tty/ vtconsole/
- graphics/ misc/ regulator/ ump/ x4412-cdev/
- [root@x4412 virtual]#
复制代码 可以看出,加载驱动后,在/sys/devices/virtual目录下生成了x4412-cdev目录。 - [root@x4412 x4412-cdev]# more /proc/devices |grep x4412
- 249 x4412-cdev
- [root@x4412 x4412-cdev]#
复制代码 这里表明设备驱动的主设备号为249。 - [root@x4412 x4412-cdev]# ls /dev/x4412-cdev -la
- crw-rw---- 1 root root 249, 0 Oct 3 09:47 /dev/x4412-cdev
- [root@x4412 x4412-cdev]#
复制代码 查看/dev下的设备节点时,有可能并不存在,执行mdev –s指令将会触发节点的生成。前面表明设备驱动的主设备号为249,次设备号为0。 执行应用程序,观察现象是否和我们期望的一致: - [root@x4412 mnt]# ./x4412-cdev-app
- read from kernel: This is x4412/ibox devboard.
- write value to kernel:
- [ 5858.796557] hello,x4412!
- [root@x4412 mnt]#
复制代码 很明显,无论是应用程序还是驱动程序,都是按照我们的要求有条不紊的执行。再来分析cdev_init,cdev_add及register_chrdev函数的区别。事实上,使用register_chrdev函数要居多,因为它用起来更加方便。事实上,register_chrdev函数的本质就是将cdev_alloc和cdev_add函数包装起来了,而cdev_alloc函数就相当于cdev_init函数,在kernel/fs/char_dev.c中可以看到register_chrdev的函数原型__register_chrdev():- int __register_chrdev(unsigned int major, unsigned int baseminor,
- unsigned int count, const char *name,
- const struct file_operations *fops)
- {
- struct char_device_struct *cd;
- struct cdev *cdev;
- int err = -ENOMEM;
-
- cd = __register_chrdev_region(major, baseminor, count, name);
- if (IS_ERR(cd))
- return PTR_ERR(cd);
-
- cdev = cdev_alloc();
- if (!cdev)
- goto out2;
-
- 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)
- goto out;
-
- cd->cdev = cdev;
-
- return major ? 0 : cd->major;
- out:
- kobject_put(&cdev->kobj);
- out2:
- kfree(__unregister_chrdev_region(cd->major, baseminor, count));
- return err;
- }
复制代码 |