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;
- }