linux设备驱动之字符设备驱动模型(1)
2015-09-12 16:15
561 查看
一:字符设备驱动
在linux下面,应用层看到的一切皆为文件(名字)所有的设备都是文件,都可以调用open,read,write来操作,而在内核中每个中每个设备有唯一的对应一个设备号;
下面我们写一个简单的字符设备驱动,再应用层我们打开一个设备,看看它是怎么来调用内核中的函数的;
首先我们使用命令mknod 创建一个名为wangcai的设备名,在应用曾打开它;
mknod /dev/设备文件名 c 主设备号 次设备号
命令 mknod wangcai c 9 0
在注册字符设备是我们需要用到这几个struct cdev,原型如下:
cdev结构体的dev_t定义了设备号,32位。高12位为主设备号,低20位为次设备号。
(1)应用层打开设备
(2)在内核中册了设备文件wangcai和方法ops
下面代码是实现字符设备的读写操作:
(1)应用层
write:
read:
(2)内核
下面代码是通过ioctl()函数来控制灯亮灯灭:
(1)应用层
(2)内核
通过上面的代码我们已经了解了字符设备驱动的原理,在linux下应用层看到的设备都只 是一个名字,应用层打开一个设备最终会调到内核中的file_operations方法来进行读写操作,如果我们只创建一个的设备的时候,我们可以对他正 常的读写,那如果当我们有两个设备时,我们是否还能正常的进行读写操作,明显是存在问题的,就是第二次的数据会将第一次的数据覆盖,因为我们只有一个数据存储的data;那么解决这个问题的方法就是封装;
下面是具体代码:
代码中有看到了container_of,再次强调掌握,还需要注意的是:
(1)每一个设备文件仅有inode结构体 ;
(2)每打开一次文件就创建一个file 结构体;
在linux下面,应用层看到的一切皆为文件(名字)所有的设备都是文件,都可以调用open,read,write来操作,而在内核中每个中每个设备有唯一的对应一个设备号;
APP (名字) |
OS (设备号) |
HW |
首先我们使用命令mknod 创建一个名为wangcai的设备名,在应用曾打开它;
mknod /dev/设备文件名 c 主设备号 次设备号
命令 mknod wangcai c 9 0
在注册字符设备是我们需要用到这几个struct cdev,原型如下:
struct cdev { struct kobject kobj; // 内嵌的kobject对象,描述设备引用计数 struct module *owner; // 所属模块,一般赋值为THIS_MODULE struct file_operations *ops; // 文件操作结构体 struct list_head list; dev_t dev; // 设备号 unsigned int count; };
cdev结构体的dev_t定义了设备号,32位。高12位为主设备号,低20位为次设备号。
(1)应用层打开设备
#include <stdio.h> #include <fcntl.h> int main() { int fd = 0; fd = open("wangcai", O_RDWR); if(fd < 0) { perror("open error"); return 1; } return 0; }
(2)在内核中册了设备文件wangcai和方法ops
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("bunfly"); int file_open(struct inode *no, struct file *fp); struct cdev wangcai;//设备 struct file_operations fops;//方法 dev_t devno; int bunfly_init() { fops.open = file_open;//调用open //wangcai.ops = &fops; cdev_init(&wangcai, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接 //wangcai.dev = (9, 0); devno = MKDEV(9, 0);//字符设备号注册 //insert_list(&wangcai); cdev_add(&wangcai, devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中 } int bunfly_exit() { printk("this is bunfly_exit\n"); } module_init(bunfly_init); module_exit(bunfly_exit); int file_open(struct inode *no, struct file *fp) { printk("this is file_open\n"); return 0; }
下面代码是实现字符设备的读写操作:
(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:
#include <stdio.h> #include <fcntl.h> #include <string.h> int main(int argc, char *argv[]) { if(argc != 2) { printf("using %s <dev>\n", argv[0]); return 1; } int fd = 0; int ret = 0; unsigned char data[1024] = {0}; fd = open(argv[1], O_RDWR); if(fd < 0) { perror("open error"); return 1; } ret = read(fd, data, 1024); if(ret < 0) { perror("write error"); return 1; } printf("data is: %s\n", data); close(fd); return 0; }
(2)内核
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("bunfly"); int file_open(struct inode *no, struct file *fp); ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t * loff); ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff); struct cdev wangcai;//设备 struct file_operations fops;//方法 dev_t devno; unsigned char data[1024] = {0}; int bunfly_init() { fops.open = file_open; fops.read = file_read; fops.write = file_write; //wangcai.ops = &fops; cdev_init(&wangcai, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接 //wangcai.dev = (9, 0); devno = MKDEV(9, 0);//字符设备号注册 //insert_list(&wangcai); cdev_add(&wangcai, devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中 } int bunfly_exit() { cdev_del(&wangcai); /*注销设备*/ unregister_chrdev_region(MKDEV(9, 0), 1); printk("this is bunfly_exit\n"); } module_init(bunfly_init); module_exit(bunfly_exit); int file_open(struct inode *no, struct file *fp) { return 0; } ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t *loff) { strcpy(buff, data); return size; } ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff) { memset(data, 0, 1024); strcpy(data, buff); return size; } 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)内核
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/io.h> #include <linux/gpio.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("bunfly"); long my_ioctl(struct file *fp, unsigned int id, unsigned long fd); int file_open(struct inode *no, struct file *fp); struct cdev wangcai; struct file_operations fops; dev_t devno; unsigned long gpio_virt; unsigned long *gpm4con, *gpm4dat; int bunfly_init() { fops.open = file_open; fops.unlocked_ioctl = my_ioctl; //wangcai.ops = &fops; cdev_init(&wangcai, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接 //wangcai.dev = (9, 0); devno = MKDEV(9, 0);//字符设备号注册 //insert_list(&wangcai); cdev_add(&wangcai, devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中 gpio_virt = ioremap(0x11000000, SZ_4K);//led物理地址到虚拟地址映射 gpm4con = gpio_virt + 0x02e0; gpm4dat = gpio_virt + 0x02e4; return 0; } int bunfly_exit() { cdev_del(&wangcai); /*注销设备*/ unregister_chrdev_region(MKDEV(9, 0), 1); printk("this is bunfly_exit\n"); return 0; } module_init(bunfly_init); module_exit(bunfly_exit); int file_open(struct inode *no, struct file *fp) { return 0; } long my_ioctl(struct file *fp, unsigned int id, unsigned long fd) { if(id == 1) { *gpm4con = 0x1111; *gpm4dat = 0x0; } if(id == 0) { *gpm4con = 0x1111; *gpm4dat = 0xf; } }
通过上面的代码我们已经了解了字符设备驱动的原理,在linux下应用层看到的设备都只 是一个名字,应用层打开一个设备最终会调到内核中的file_operations方法来进行读写操作,如果我们只创建一个的设备的时候,我们可以对他正 常的读写,那如果当我们有两个设备时,我们是否还能正常的进行读写操作,明显是存在问题的,就是第二次的数据会将第一次的数据覆盖,因为我们只有一个数据存储的data;那么解决这个问题的方法就是封装;
下面是具体代码:
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("bunfly"); int file_open(struct inode *no, struct file *fp); ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t *loff); ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff); struct file_operations fops;//方法 /*封装*/ struct bunfly_cdev { dev_t devno;//设备号 struct cdev cdev; unsigned char data[1024]; }; int bunfly_init() { fops.open = file_open; fops.read = file_read; fops.write = file_write; struct bunfly_cdev wangcai; cdev_init(&wangcai.cdev, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接 wangcai.devno = MKDEV(9, 0);//注册设备号 cdev_add(&wangcai.cdev, wangcai.devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中 struct bunfly_cdev tugou; cdev_init(&tugou.cdev, &fops); tugou.devno = MKDEV(9, 1); cdev_add(&tugou.cdev, tugou.devno, 1); return 0; } int bunfly_exit() { printk("this is bunfly_exit\n"); return 0; } module_init(bunfly_init); module_exit(bunfly_exit); int file_open(struct inode *no, struct file *fp) { struct cdev *addr = no->i_cdev;//找到struct cdev dev 在struct bunfly_cdev中的地址 struct bunfly_cdev *this = container_of(addr, struct bunfly_cdev, cdev); fp->private_data = this; //父类在子类中的地址 //子类类型 return 0; } ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t *loff) { struct bunfly_cdev *this = fp->private_data; strcpy(buff,this-> data); return size; } ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff) { struct bunfly_cdev *this = fp->private_data; memset(this->data, 0, 1024); strcpy(this->data, buff); return size; }
代码中有看到了container_of,再次强调掌握,还需要注意的是:
(1)每一个设备文件仅有inode结构体 ;
(2)每打开一次文件就创建一个file 结构体;
相关文章推荐
- redhat linux使用Centos yum源
- linux使用脚本自动连接数据库
- Centos6.x/7.x LNMP 环境安装常见错误集锦
- centos 启动出现 UNEXPECTED INCONSISTENCY RUN fsck MANUALLY
- Linux 学习:find命令习题
- linux下如何挂载光驱
- “TI门外汉”的觉醒~~
- Linux 学习:文件查找的使用
- VMWare下linux安装及中文语言包安装详细步骤
- Centos 6.x/7.x yum安装php5.6.X(最新版)
- 鸟哥的Linux私房菜5.1部分笔记:linux注销、x window与命令行模式的切换(新手接触linux)
- linux常用命令-关机重启
- linux内核中断之看门狗
- Linux内核笔记——内存管理之块内存分配
- Linux 系统安全 及 lnmp 安装
- linux 学习笔记之权限管理命令
- linux学习笔记之文件搜索命令
- linux 学习笔记之文件的压缩和解压
- linux 学习之帮助命名
- 个人学习笔记---linux原子操作的实现原理