字符设备驱动程序的基本步骤
2013-08-29 00:17
393 查看
字符设备驱动程序的基本步骤 字符设备驱动程序的基本步骤 一.设备号 对字符设备的访问是通过文件系统内的设备名称来访问的,设备名称位于目录/dev下.为了便于系统管理,设置了和设备名称一一对应的设备号,它分为主设备号和次设备号.通常来说,主设备号标示了设备对应的驱动程序,次设备号则用来分辨拥有同一个主设备号的的各个不同设备. 在内核中,设备号使用类型dev_t来保存,它包括了主设备号和次设备号.dev_t是一个32位的整数,其中的12位用来标示主设备号,其余的20位用来标示次设备号.我们可以使用两个宏来获得设备的主设备号及次设备号: MAJOR(dev_t dev_id); MINOR(dev_t dev_id); 将主设备号和次设备号转换为dev_t类型,则可以使用下面的宏: MKDEV(int major, int minor); 其中,major为主设备号,minor为次设备号. 二.分配设备号 在建立一个字符设备之前.首先要申请设备号,完成该功能的函数有两个,都包含在头文件中.下面分别来看这两个文件: 1.int register_chrdev_region(dev_t first, unsigned int count, char *name); 其中, first为要分配的设备编号范围的起始值,经常被置零.count则是所请求的连续设备编号的个数,这意味着只能申请连续的设备编号. 2.int alloc_chrdev_region(dev_t *dev, unsigned firstminor, int count, char *name); 其中dev用于保存申请成功后动态分配的第一个设备号, firstminor则是请求使用的第一个次设备号.其余与上个函数相同. 三.定义并初始化file_operations结构体. file_operations结构体用于连接设备号和驱动程序的操作.在该结构体的内部包含了一组函数指针,这些函数用来实现系统调用.通常情况下,要注册如下的几个函数: 1.struct module *owner:用来指向拥有该结构体的模块. 2.ssize_t read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops):用来从设备中读取数据.其中: filp为文件属性结构体指针. buf为用户态函数使用的字符内存缓冲. count为要读取的数据数. f_ops为文件指针的偏移量. 2.ssize_t write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops):用来向设备输入数据.各函数的含义与上个函数相同. 3.int open(struct inode *inode, struct file *):该函数用来打开一个设备文件. 4.int release(struct inode *inode, struct file *):该函数用来关闭一个设备文件. 该结构体的初始化形式如下例: struct file_operations scull_fops = { .owner = THIS_MODULE, .read = read, .write = write, .open = open, .release = release, } 四.字符设备的注册. 内核内部使用struct cdev结构来表示字符设备.在内核调用设备的操作之前,必须分配或注册一个或者多个该结构体.该结构体包含在头文件中.一般的步骤如下: 首先定义该结构体: struct cdev my_cdev; 然后即可以初始化该结构,使用如下的函数初始化: int cdev_init(struct cdev *dev, struct file_operations *fops). 然后定义该结构体中的一个所有者字段: my_cdev.owner = THIS_MODULE; 最后即可以向模块添加该结构体: int cdev_add(struct cdev *dev, dev_t dev_num, usigned int count).其中dev是cdev结构体,dev_num是该设备对应的第一个设备编号, count则是与该设备关联的设备编号数量. 五.移除字符设备 vo ab85 id cdev_del(struct cdev *dev); 六.注销设备号. unregister_chrdev_region(dev_t first, unsigned int count); 以上两个函数一般用于模块出口函数中. linux 一个简单的字符设备驱动例子 先包含这些头文件 #include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #define BUFFERSIZE 200 #define DEVICE_MAJOR 250 /*设置一个主设备号*/ static int device_major = DEVICE_MAJOR; 定义一个与字符设备对应的结构体 struct my_cdev { struct cdev cdev; /*cdev结构体,与字符设备对应*/ /*下面可以定义一些与字符设备相关的数据*/ usigned char mem[BUFFERSIZE]; }; struct my_cdev *my_cdevp; /*设备结构体指针*/ int my_cdev_open( struct inode *node, struct file *filp ) { /*将设备结构体指针赋给文件私有数据指针*/ filp->private_data = my_cdevp /*这样可以通过文件私有数据指针得到设备结构体*/ return 0; } int my_cdev_release( struct inode *node, struct file *filp ) { return 0; } static size_t my_cdev_read( struct file *filp, char __user *buf, size_t size, loff_t *ppos ) { unsigned long p = *ppos; /*文件当前位置*/ unsigned int count = size; /*要读取的长度*/ int ret = 0; struct my_cdev *dev = filp->private_data; /*通过文件私有数据指针得到设备结构体,和前面的open对应*/ if ( p >= BUFFERSIZE ) return count ? -ENXIO:0; if ( count > BUFFERSIZE - p ) count = BUFFERSIZE - p; /*内核空间->用户空间*/ if ( copy_to_user(buf, (void *)(dev->mem + p), count) ) { ret = -EFAULT; } else { *ppos += count; ret = count; } return ret; } static size_t my_cdev_write( struct file *filp, const char __user *buf, size_t size, loff_t *ppos ) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struce my_cdev *dev = filp->private_data; if( p >= BUFFERSIZE ) return count ? -ENIX : 0; if (count > BUFFERSIZE - p ) count = BUFFERSIZE - p; if ( copy_from_user( dev->mem + p, buf, count)) { ret = -EFAULT; } else { *ppos += count; ret = count; } return ret } static loff_t my_cdev_llseek(struct file *filp, loff_t offset, int orig) { loff_t ret = 0; switch (orig) { case 0: if ( offset < 0 ) { ret = -EINVAL; break; } if (offset > BUFFERSIZE) { ret = -EINVAL; break; } filp->f_pos = (unsigned int)offset; ret = fips->f_pos; break; default: ret = -EINVAL; break; } return ret; } /*文件操作结构体*/ static const struct file_operstions my_cdev_fops = { .owner = THIS_MODULE, .open = my_cdev_open, .release = my_cdev_release, .read = my_cdev_read, .write = my_cdev_write, .llseek = my_cdev_llseek, }; /*初始化并注册cdev,就是注册我们自己的字符设备*/ static void my_cdev_setup( struct my_cdev *dev, int index ) { int err; dev_t devno = MKDEV(DEVICE_MAJOR, index); cdev_init( &dev->cdev, &my_cdev_fops ); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &my_cdev_fops; /*我认为在cdev_init里应该做过赋值,应该可以不用写*/ err = cdev_add( &dev->cdev, devno, 1 ); if (err) printk(KERN_NOTICE "Error %d adding LED%d", err, index); } static int __init my_cdev_init(void) { int result; dev_t devno = MKDEV(DEVICE_MAJOR, index); /*申请设备号*/ if ( device_major ) { result = register_chrdev_region(devno, 1, "my_cdev"); } else { result = alloc_chrdev_region( &devno, 0, 1, "my_cdev"); device_major = MAJOR(devno); } if ( result < 0 ) { return result; } my_cdevp = kmalloc(sizeof(struct my_cdev), GFP_KERNEL); if ( !my_cdevp ) { result = -ENOMEM; goto fail_malloc; } memset(my_cdevp, 0, sizeof(struct my_cdev)); my_cdev_setup(my_cdevp, 0); return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } static void __exit my_cdev_exit(void) { cdev_del(&my_cdevp->cdev); kfree(my_cdevp); unregister_chrdev_region(MKDEV(device_major, 0), 1); } MODULE_AUTHOR("Song Baohua"); MODULE_LICENSE("Dual BSD/GPL"); module_init(my_cdev_init); module_exit(my_cdev_exit); ********************************************************************************* 然后可以写一个简单的内核模块的Makefile,编译make后生成mycdev.ko文件,insmod mycdev.ko, 装上我们自己的驱动,注意有可能在装的时候提示说device busy什么的,这就是我们前面指定的主设备号现在 有设备在用,我们就在重新指定一个在编译。 那怎么看有那些设备呢?可以用cat /proc/devices,就可以查看已经有哪些主设备号已被占用。 然后就可以自己先创建一个虚拟的字符设备mknod /dev/mycdev c 250 0 ****************************************************** 下面就可以自己写一个应用程序来看我们自己的字符设备驱动是否OK。 #include <stdio.h> #include <fctl.h> #include <string.h> #include <sys/stat.h> #define BUFFERSIZE 200 int main( void ) { int fp = 0 ; char str[BUFFERSIZE]; fp = open( "/dev/mycdev", O_RDWR, S_IRUSR|S_IWUSR ); if ( !fp ) { printf("Open device failed\n"); return -1; } write( fp, "Hello, my devices", strlen("Hello, my devices") ); lseek( fp, 0, 0 );/*修改字符设备里字符数组的位置,将字符数据位置设到开始的位置,不然下面的read操作将读不到数据*/ read( fp, str, BUFFERSIZE ); printf("Read content: %s\n", str ); close(fp); } gcc -o sample sample.c 最后运行./sample 应该会输出:Read content: Hello, my devices 总结一下,我个人觉得字符设备驱动相关的API和数据结构。 1. struct cdev 一个设备对应一个这个的数据结构,结构体是重要的两个字段ops 和dev(设备号) 2. struct file_opertions 文件操作结构体 3. cdev_init(struct cdev *, struct file_opertions *) 主要就是把字符设备和对这个设备的文件操作结构体对应起来 4. cdev_add(struct cdev *, dev_t, unsigned) 注册设备 5. register_chrdev_region(dev_t, unsigned, const char *name)/alloc_chrdev_region() 申请设备号,为注册设备(cdev_add())准备 6. 就是内核空间的数据和用户空间的数据交换 |
相关文章推荐
- 字符设备驱动程序的基本步骤
- Linux字符设备驱动程序编写基本流程
- Linux字符设备驱动程序编写基本流程
- 字符设备开发的基本步骤
- 最基本的字符设备驱动程序框架
- 简单字符设备驱动程序的操作步骤
- Linux字符设备驱动程序编写基本流程
- Linux字符设备驱动程序编写基本流程
- 字符设备开发的基本步骤
- Linux字符设备驱动程序编写基本流程
- 从零开始写linux字符设备驱动程序(一)(基于友善之臂tiny4412开发板)
- Linux字符设备驱动程序开发
- 从零开始写linux字符设备驱动程序(四)(基于友善之臂tiny4412开发板)
- linux字符设备驱动程序框架
- 字符设备驱动程序按键驱动---中断方式
- Linux 2.6 字符设备驱动程序
- Linux设备驱动开发基础---字符设备驱动程序开发之mini2440_ADC驱动
- 深入浅出Linux字符设备驱动程序解析(转自http___linux_chinaitlab_com_driver_720255_html)
- Linux下字符设备驱动程序的结构
- 带Ioctl命令的字符设备驱动程序