linux设备驱动之字符设备驱动模型(1)
一:字符设备驱动
在linux下面,应用层看到的一切皆为文件(名字)所有的设备都是文件,都可以调用open,read,write来操作,而在内核中每个中每个设备有唯一的对应一个设备号;
APP (名字) |
OS (设备号) |
HW |
下面我们写一个简单的字符设备驱动,再应用层我们打开一个设备,看看它是怎么来调用内核中的函数的;
首先我们使用命令mknod 创建一个名为wangcai的设备名,在应用曾打开它;
mknod /dev/设备文件名 c 主设备号 次设备号
命令 mknod wangcai c 9 0
在注册字符设备是我们需要用到这几个struct cdev,原型如下:
1 struct cdev 2 { 3 struct kobject kobj; // 内嵌的kobject对象,描述设备引用计数 4 struct module *owner; // 所属模块,一般赋值为THIS_MODULE 5 struct file_operations *ops; // 文件操作结构体 6 struct list_head list; 7 dev_t dev; // 设备号 8 unsigned int count; 9 };
cdev结构体的dev_t定义了设备号,32位。高12位为主设备号,低20位为次设备号。
(1)应用层打开设备
1 #include <stdio.h> 2 #include <fcntl.h> 3 4 int main() 5 { 6 int fd = 0; 7 fd = open("wangcai", O_RDWR); 8 if(fd < 0) { 9 perror("open error"); 10 return 1; 11 } 12 13 return 0; 14 }
(2)在内核中册了设备文件wangcai和方法ops
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/cdev.h> 4 #include <linux/fs.h> 5 6 MODULE_LICENSE("GPL"); 7 MODULE_AUTHOR("bunfly"); 8 int file_open(struct inode *no, struct file *fp); 9 10 struct cdev wangcai;//设备 11 struct file_operations fops;//方法 12 dev_t devno; 13 14 int bunfly_init() 15 { 16 fops.open = file_open;//调用open 17 //wangcai.ops = &fops; 18 cdev_init(&wangcai, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接 19 //wangcai.dev = (9, 0); 20 devno = MKDEV(9, 0);//字符设备号注册 21 //insert_list(&wangcai); 22 cdev_add(&wangcai, devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中 23 24 } 25 26 int bunfly_exit() 27 { 28 printk("this is bunfly_exit\n"); 29 } 30 31 module_init(bunfly_init); 32 module_exit(bunfly_exit); 33 34 int file_open(struct inode *no, struct file *fp) 35 { 36 printk("this is file_open\n"); 37 38 return 0; 39 }
下面代码是实现字符设备的读写操作:
(1)应用层
write:
#include <stdio.h> #include <fcntl.h> #include <string.h> int main(int argc, char *argv[]) { if(argc < 3) { printf("using %s <dev> <msg>\n", argv[0]); return 1; } int fd = 0; int ret = 0; fd = open(argv[1], O_RDWR); if(fd < 0) { perror("open error"); return 1; } ret = write(fd, argv[2], strlen(argv[2])); if(ret < 0) { perror("write error"); return 1; } close(fd); return 0; }
read:
1 #include <stdio.h> 2 #include <fcntl.h> 3 #include <string.h> 4 5 int main(int argc, char *argv[]) 6 { 7 if(argc != 2) { 8 printf("using %s <dev>\n", argv[0]); 9 return 1; 10 } 11 12 int fd = 0; 13 int ret = 0; 14 unsigned char data[1024] = {0}; 15 16 fd = open(argv[1], O_RDWR); 17 if(fd < 0) { 18 perror("open error"); 19 20 return 1; 21 } 22 23 ret = read(fd, data, 1024); 24 if(ret < 0) { 25 perror("write error"); 26 return 1; 27 } 28 29 printf("data is: %s\n", data); 30 31 close(fd); 32 33 return 0; 34 }
(2)内核
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/cdev.h> 4 #include <linux/fs.h> 5 6 MODULE_LICENSE("GPL"); 7 MODULE_AUTHOR("bunfly"); 8 9 int file_open(struct inode *no, struct file *fp); 10 ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t * loff); 11 ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff); 12 13 struct cdev wangcai;//设备 14 struct file_operations fops;//方法 15 dev_t devno; 16 unsigned char data[1024] = {0}; 17 18 int bunfly_init() 19 { 20 fops.open = file_open; 21 fops.read = file_read; 22 fops.write = file_write; 23 24 //wangcai.ops = &fops; 25 cdev_init(&wangcai, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接 26 //wangcai.dev = (9, 0); 27 devno = MKDEV(9, 0);//字符设备号注册 28 //insert_list(&wangcai); 29 cdev_add(&wangcai, devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中 30 31 } 32 33 int bunfly_exit() 34 { 35 cdev_del(&wangcai); /*注销设备*/ 36 unregister_chrdev_region(MKDEV(9, 0), 1); 37 printk("this is bunfly_exit\n"); 38 } 39 40 module_init(bunfly_init); 41 module_exit(bunfly_exit); 42 43 int file_open(struct inode *no, struct file *fp) 44 { 45 return 0; 46 } 47 ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t *loff) 48 { 49 strcpy(buff, data); 50 return size; 51 } 52 53 ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff) 54 { 55 memset(data, 0, 1024); 56 strcpy(data, buff); 57 return size; 58 } 59
下面代码是通过ioctl()函数来控制灯亮灯灭:
(1)应用层
#include <stdio.h> #include <string.h> #include <fcntl.h> int main(int argc, char *argv[]) { if(argc != 3) { printf("using %s <devname> 1:0\n", argv[0]); return 1; } int fd = 0; fd = open(argv[2], O_RDWR); if(fd < 0) { perror("open"); return 1; } ioctl(fd, atoi(argv[2])); close(fd); return 0; }
(2)内核
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/cdev.h> 4 #include <linux/fs.h> 5 #include <linux/io.h> 6 #include <linux/gpio.h> 7 8 MODULE_LICENSE("GPL"); 9 MODULE_AUTHOR("bunfly"); 10 11 long my_ioctl(struct file *fp, unsigned int id, unsigned long fd); 12 int file_open(struct inode *no, struct file *fp); 13 14 struct cdev wangcai; 15 struct file_operations fops; 16 dev_t devno; 17 unsigned long gpio_virt; 18 unsigned long *gpm4con, *gpm4dat; 19 20 int bunfly_init() 21 { 22 fops.open = file_open; 23 fops.unlocked_ioctl = my_ioctl; 24 25 //wangcai.ops = &fops; 26 cdev_init(&wangcai, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接 27 //wangcai.dev = (9, 0); 28 devno = MKDEV(9, 0);//字符设备号注册 29 //insert_list(&wangcai); 30 cdev_add(&wangcai, devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中 31 32 33 gpio_virt = ioremap(0x11000000, SZ_4K);//led物理地址到虚拟地址映射 34 gpm4con = gpio_virt + 0x02e0; 35 gpm4dat = gpio_virt + 0x02e4; 36 37 return 0; 38 } 39 40 int bunfly_exit() 41 { 42 cdev_del(&wangcai); /*注销设备*/ 43 unregister_chrdev_region(MKDEV(9, 0), 1); 44 printk("this is bunfly_exit\n"); 45 46 return 0; 47 } 48 49 module_init(bunfly_init); 50 module_exit(bunfly_exit); 51 52 int file_open(struct inode *no, struct file *fp) 53 { 54 return 0; 55 } 56 57 long my_ioctl(struct file *fp, unsigned int id, unsigned long fd) 58 { 59 if(id == 1) { 60 *gpm4con = 0x1111; 61 *gpm4dat = 0x0; 62 } 63 if(id == 0) { 64 *gpm4con = 0x1111; 65 *gpm4dat = 0xf; 66 } 67 }
通过上面的代码我们已经了解了字符设备驱动的原理,在linux下应用层看到的设备都只 是一个名字,应用层打开一个设备最终会调到内核中的file_operations方法来进行读写操作,如果我们只创建一个的设备的时候,我们可以对他正 常的读写,那如果当我们有两个设备时,我们是否还能正常的进行读写操作,明显是存在问题的,就是第二次的数据会将第一次的数据覆盖,因为我们只有一个数据存储的data;那么解决这个问题的方法就是封装;
下面是具体代码:
1 #include <linux/init.h> 2 #include <linux/module.h> 3 #include <linux/cdev.h> 4 #include <linux/fs.h> 5 6 MODULE_LICENSE("GPL"); 7 MODULE_AUTHOR("bunfly"); 8 9 int file_open(struct inode *no, struct file *fp); 10 ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t *loff); 11 ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff); 12 13 struct file_operations fops;//方法 14 15 /*封装*/ 16 struct bunfly_cdev { 17 dev_t devno;//设备号 18 struct cdev cdev; 19 unsigned char data[1024]; 20 }; 21 22 int bunfly_init() 23 { 24 fops.open = file_open; 25 fops.read = file_read; 26 fops.write = file_write; 27 28 struct bunfly_cdev wangcai; 29 cdev_init(&wangcai.cdev, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接 30 wangcai.devno = MKDEV(9, 0);//注册设备号 31 cdev_add(&wangcai.cdev, wangcai.devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中 32 33 struct bunfly_cdev tugou; 34 cdev_init(&tugou.cdev, &fops); 35 tugou.devno = MKDEV(9, 1); 36 cdev_add(&tugou.cdev, tugou.devno, 1); 37 38 return 0; 39 } 40 41 int bunfly_exit() 42 { 43 printk("this is bunfly_exit\n"); 44 45 return 0; 46 } 47 48 module_init(bunfly_init); 49 module_exit(bunfly_exit); 50 51 int file_open(struct inode *no, struct file *fp) 52 { 53 struct cdev *addr = no->i_cdev;//找到struct cdev dev 在struct bunfly_cdev中的地址 54 struct bunfly_cdev *this = container_of(addr, struct bunfly_cdev, cdev); 55 fp->private_data = this; //父类在子类中的地址 //子类类型 56 57 return 0; 58 } 59 ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t *loff) 60 { 61 struct bunfly_cdev *this = fp->private_data; 62 strcpy(buff,this-> data); 63 64 return size; 65 } 66 67 ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff) 68 { 69 struct bunfly_cdev *this = fp->private_data; 70 memset(this->data, 0, 1024); 71 strcpy(this->data, buff); 72 73 return size; 74 } 75
代码中有看到了container_of,再次强调掌握,还需要注意的是:
(1)每一个设备文件仅有inode结构体 ;
(2)每打开一次文件就创建一个file 结构体;