http://blog.csdn.net/yangjin_unique/article/details/8222465
目的:将内核内存的一块作为字符设备,用户可通过这些调用来读写这段内存。
总结:
1、设备号
主设备号标识设备对应的驱动程序,次设备号由内核使用,用于确定设备文件所指的设备。
通过次设备号获得一个指向内核设备的直接指针,也可将此设备号当作设备本地数组的索引。
设备编号用dev_t表示(linux/types.h 32位,其中12位表示主设备号,20位表示次设备号)。
由dev_t获得主设备号或次设备号:MAJOR(dev_t dev); MINOR(dev_t dev)
已知主设备号和次设备号来获取dev_t类型:MKDEV(int major, int minor)
获取一个或多个设备编号:int register_chrdev_region(dev_t first, unsigned int count, char *name);(静态分配,事先已知道设备号)
动态分配设备编号:int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 调用成功后dev会保存已分配的第一个编号。
释放设备编号:void unregister_chrdev_region(dev_t first, unsigned int count);
接下来,驱动程序需要将设备编号和内部函数连接起来。
注:(下一步可尝试采用动态分配设备号)
动态分配设备号缺点:不能预先创建设备节点(因为分配的设备号不能保证始终一致)。
2、文件操作file_operations:
这些操作将与设备编号连接起来。
__user:用于文档,表明该指针是一个用户空间地址。
主要成员:open, ioctl, read, write, llseek
3、struct file结构 linux/fs.h 文件描述符
每打开一个文件,内核就会创建一个对应的file结构,在open()时创建,同时会传递给在该文件上进行操作的所有函数(因为file结构中包含file_operations结构,而该结构包含了所有驱动操作的函数)。
内核中用filp作为执行file结构的指针。
主要成员:
Mode_t f_mode; loff_t f_pos; struct file_operations *f_pos; void private_data;
4、inode结构
对单个文件只有一个inode,而可能有多个file(由于fork,dup操作)。
主要成员:
dev_t i_rdev; 对表示设备文件的inode结构,该字段包含真正的设备编号。
struct cdev *i_cdev; 该结构表示字符设备的内核的内部结构,当inode指向一个字符设备文件时,该字段包含指向struct cdev结构的指针。
从inode中获取设备号: iminor(struct inode *inode); imajor(inode);
5、字符设备注册 /linux/cdev.h
内核使用struct cdev结构表示字符设备,所以在内核调用该设备操作之前,需要分配并注册一个或者多个该结构。
注册有两种方式:
新方法:
1)定义字节的结构:
struct my_dev {
struct cdev cdev; //此处如果定义的是指针类型,则需要申请分配内存
}my_dev;
//my_dev ->cdev = cdev_alloc(); //如果cdev是指针则需要这一步
my_dev->cdev.ops = &my_fops;
my_dev->cdev.owner = THIS_MODULE;
2)再调用cdev_init(struct cdev *cdev, struct file_operations *fops);
3)调用cdev_add(struct cdev *dev, dev_t num, unsigned int count);
上面每一步都要判断函数调用是否出错。
旧办法(老接口):
注册:int register_chrdev(); 移除:int unregister_chrdev()
6、各操作函数实现
1)open int (*open) (struct inode *inode, struct file *filp)
完成以下工作:传入一个inode,创建一个file结构
n 检查设备特定错误(如未就绪);
n 如果设备首次打开,则进行初始化;
n 必要时更新f_op指针;
n 分配并填写filp->private_data;
注:inode结构是传入的参数,对应一个特定的设备(这就是为什么要在/dev下mknodde 原因),而file结构的filp是要修改的参数(传出),对应该设备的一个文件描述符,也就是一个inode可能有多个file描述符,而每个描述符需要保存inode的信息,即存放在filp->private_data中。
2)release int (*release) (struct inode *inode, struct file *filp)
完成工作:传入一个inode,释放这个file结构
n 释放由open分配的、保存在filp->private_data中的内容;
n 在最后一次close时关闭设备
dup 和fork都会在不调用open时创建新的file结构(对应同一个inode)。
注:并不是每个close调用都会调用release;只有真正释放设备数据结构的close调用才会调用release。内核对每个file结构维护其被使用次数的计数器,无论是fork还是dup,都不会创建新的数据结构(只会有open创建),它们只是增加已有结构中的计数器而已。只有在file结构的计数为0时,close才会调用release。
3)read ssize_t read(struct file *filp, char __user *buf, count, loff_t *offp)
完成工作:传入file,将count个字节数据写入用户地址buf,修改loff_t
由copy_to_user()实现
返回值说明:
n 等于count:所请求的字节数读取成功;
n 返回值为正,但小于count:只读取了部分数据;
n 为0:已经达到文件尾;
n 负值:出错
4)write ssize_t write(struct file *filp, char __user *buf, count, offp);
由copy_from_user()实现
返回值同上。
1)驱动代码 Demo.h #ifndef _DEMO_H_ #define _DEMO_H_ #include <linux/ioctl.h> /*Macros to help debuging*/ #undef PDEBUG #ifdef DEMO_DEBUG #ifdef __KERNEL__ #define PDEBUG(fmt, args...) printk(KERN_DEBUG "DEMO:" fmt,## args) #else #define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) #endif #else #define PDEBUG(fmt, args...) #endif #define DEMO_MAJOR 224 #define DEMO_MINOR 0 #define COMMAND1 1 #define COMMAND2 2 struct demo_dev { struct cdev cdev; }; ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); loff_t demo_llseek(struct file *filp, loff_t off, int whence); int demo_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); #endif demo.c #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/types.h> #include <linux/fcntl.h> #include <linux/cdev.h> #include <linux/version.h> #include <linux/vmalloc.h> #include <linux/ctype.h> #include <linux/pagemap.h> #include "demo.h" MODULE_AUTHOR("Yangjin"); MODULE_LICENSE("Dual BSD/GPL"); struct demo_dev *demo_devices; static unsigned char demo_inc = 0;//全局变量,每次只能打开一个设备 static u8 demo_buffer[256]; int demo_open(struct inode *inode, struct file *filp) { struct demo_dev *dev; if (demo_inc > 0) return -ERESTARTSYS; demo_inc++; dev = container_of(inode->i_cdev, struct demo_dev, cdev); filp->private_data = dev; return 0; } int demo_release(struct inode *inode, struct file *filp) { demo_inc--; return 0; } ssize_t demo_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { int result; loff_t pos = *f_pos; //pos: offset if (pos >= 256) { result = 0; goto out; } if (count > (256 - pos)) count = 256 - pos; pos += count; if (copy_to_user(buf, demo_buffer+*f_pos, count)) { count = -EFAULT; goto out; } *f_pos = pos; out: return count; } ssize_t demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { ssize_t retval = -ENOMEM; loff_t pos = *f_pos; if (pos > 256) goto out; if (count > (256 - pos)) count = 256 - pos; pos += count; if (copy_from_user(demo_buffer+*f_pos, buf, count)) { retval = -EFAULT; goto out; } *f_pos = pos; retval = count; out: return retval; } int demo_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { if (cmd == COMMAND1) { printk("ioctl command 1 successfully\n"); return 0; } if (cmd == COMMAND2) { printk("ioctl command 2 successfully\n"); return 0; } printk("ioctl error\n"); return -EFAULT; } loff_t demo_llseek(struct file *filp, loff_t off, int whence) { loff_t pos; pos = filp->f_pos; switch (whence) { case 0: pos = off; break; case 1: pos += off; break; case 2: default: return -EINVAL; } if ((pos > 256) || (pos < 0)) return -EINVAL; return filp->f_pos = pos; } struct file_operations demo_fops = { .owner = THIS_MODULE, .llseek = demo_llseek, .read = demo_read, .write = demo_write, .ioctl = demo_ioctl, .open = demo_open, .release = demo_release, }; void demo_cleanup_module(void) { dev_t devno = MKDEV(DEMO_MAJOR, DEMO_MINOR); if (demo_devices) { cdev_del(&demo_devices->cdev); kfree(demo_devices); } unregister_chrdev_region(devno, 1); } Init module流程: 1)注册设备号MKDEV; 2)注册设备驱动程序,即初始化cdev结构(嵌入到demo_devices结构中) int demo_init_module(void) { int result; dev_t dev = 0; dev = MKDEV(DEMO_MAJOR, DEMO_MINOR); result = register_chrdev_region(dev, 1, "DEMO"); if (result < 0) { printk(KERN_WARNING "DEMO: can't get major %d\n", DEMO_MAJOR); return result; } demo_devices = kmalloc(sizeof(struct demo_dev), GFP_KERNEL); if (!demo_devices) { result = -ENOMEM; goto fail; } memset(demo_devices, 0, sizeof(struct demo_dev)); cdev_init(&demo_devices->cdev, &demo_fops); demo_devices->cdev.owner = THIS_MODULE; demo_devices->cdev.ops = &demo_fops; //将创建的字符设备与file_operations中各函数操作连接起来 result = cdev_add(&demo_devices->cdev, dev, 1); if (result) { printk(KERN_NOTICE "error %d adding demo\n", result); goto fail; } return 0; fail: demo_cleanup_module(); return result; } module_init(demo_init_module); module_exit(demo_cleanup_module);
2)加载驱动insmod demo.ko,再使用lsmod或cat /proc/modules查看驱动是否安装;
3)创建设备节点:mknod /dev/yangjin c 224 0;注意:此处的节点设备号要与驱动程序中的注册的设备号相同。
4)再编写应用程序测试代码:
用户测试代码:
#include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <linux/rtc.h> #include <linux/ioctl.h> #include <stdio.h> #include <stdlib.h> #define COMMAND1 1 #define COMMAND2 2 int main() { int fd; int i; char data[256] = {0}; int retval; fd = open("/dev/yangjin", O_RDWR); if (fd == 1) { perror("open error\n"); exit(-1); } printf("open /dev/yangjin successfully\n"); retval = ioctl(fd, COMMAND1, 0); if (retval == -1) { perror("ioctl error\n"); exit(-1); } printf("ioctl command 1 successfully\n"); retval = write(fd, "yangjin", 7); if (retval == -1) { perror("write error\n"); exit(-1); } retval = lseek(fd, 0, 0); if (retval == -1) { perror("lseek error\n"); exit(-1); } retval = read(fd, data, 10); if (retval == -1) { perror("read error\n"); exit(-1); } printf("read successfully: %s\n", data); close(fd); return 0; }