/*********************************************************************************** * * Makefile,open,read,write,lseek,poll,ioctl,fasync * * 声明: * 1. 本系列文档是在vim下编辑,请尽量是用vim来阅读,在其它编辑器下可能会 * 不对齐,从而影响阅读. * * * 2015-3-9 阴 深圳 尚观 Var 曾剑锋 **********************************************************************************/ \\\\\\\\\\\\\\--*目录*--////////////// | 一. Makefile大致写法: | 二. 获取进程task_struct的方法: | 三. open 大致写法: | 四. read 大致写法: | 五. write 大致写法: | 六. lseek 大致写法: | 七. poll 大致写法: | 八. ioctl 大致写法: | 九. close 大致写法: | 十. fasync 大致写法: | 十一. 等待队列API: | 十二. 驱动wait_queue poll fasync: | 十三. 应用wait_queue poll fasync: \\\\\\\\\\\\\\\\\\\/////////////////// 一. Makefile大致写法: ... # 定义一个CC变量,赋值为arm-linux- CC = arm-linux- # .c文件依赖的.h文件存放的目录,这里是:当前目录下include文件夹 INCLUDE_DIRS = include # 如果变量objs在此之前未声明定义,那么就声明定义,并赋值objs为hello.o # 如果变量objs在此之前已声明定义,那么把hello.o放在objs字符串后面 objs += hello.o # 1. hello : 下面命令要生成的目标,当然也有可能是伪指令; # 2. $(objs) : 依赖文件,只有依赖文件都存在的情况下才会执行下面的指令; # 3. $(CC)gcc $< -o $@ : 编译依赖文件产生目标文件; # 1. $(CC) : 取CC变量里的值; # 2. $< : 依赖文件($(objs)) # 3. $@ : 目标文件(hello) hello : $(objs) $(CC)gcc $< -o $@ # 1. 生成的目标文件简写,将当前文件下所有的.c编译成对应的.o文件; # 2. -I$(INCLUDE_DIRS) : .c编译时需要的头文件所在的目录 %.o : %.c $(CC)gcc -c $< -o $@ -I$(INCLUDE_DIRS) # 伪命令声明,这样即使存在clean文件,也会把clean当命令使用 .PHONY:clean clean: rm *.o hello ... 二. 获取进程task_struct的方法: ... int ret; struct thread_info *info; struct task_struct *task; /** * 获取当前进程的进程描述符(PCB)算法,主要是因为thread_info里有task_struct指针, * 而thread_info放在了内核线程栈的起始地方,而内核线程栈是8k对齐, * 所以thread_finfo的首地址是: ((unsigned int)&ret & ~(0x2000 - 1)) */ info = (void *)((unsigned int)&ret & ~(0x2000 - 1)); task = info->task; printk("pid = %d, [%s]\n", task->pid, task->comm); /* task->comm 运行的程序名 */ // current 当前进程的PCB指针,系统自带提供的全局变量 printk("pid = %d, [%s]\n", current->pid, current->comm); ... 三. open 大致写法: 1. int (*open) (struct inode *, struct file *); 2. 代码模型: static int test_open(struct inode *inode, struct file *file) { /** * 一般open函数里面都要把私有数据绑定到file->private_data上 */ test_t *p; p = container_of(file->f_op, test_t, fops); file->private_data = p; //声明当前设备不支持llseek方法调整读写位置 //nonseekable_open(inode, file); return 0; } 四. read 大致写法: 1. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 2. 代码模型: static ssize_t test_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { int ret; test_t *p = file->private_data; /** * 检查可读数据是否足够 */ if(count > BUF_SIZE - *pos) count = BUF_SIZE - *pos; ret = copy_to_user(buf, p->buf + *pos, count); if(ret) return -EFAULT; *pos += count; return count; //返回读到的数据个数 } 五. write 大致写法: 1. ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 2. 代码模型: static ssize_t test_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { test_t *p = file->private_data; if(p->is_empty) { /* 进入深度睡眠其中方法中的一种 */ current->state = TASK_UNINTERRUPTIBLE; schedule(); } return count; } 六. lseek 大致写法: 1. loff_t (*llseek) (struct file *, loff_t, int); 2. 2.6版本内核不实现该方法的话,使用内核默认的llseek函数,当前版本必须实现该方法; 3. 代码模型: static loff_t test_llseek(struct file *file, loff_t offset, int whence) { loff_t pos = file->f_pos; switch(whence) { case SEEK_SET: pos = offset; break; case SEEK_CUR: pos += offset; break; case SEEK_END: pos = BUF_SIZE + offset; break; default: return -EINVAL; //参数非法 } file->f_pos = pos; //返回lseek之后的文件读写位置(相对文件开始处) return pos; } 七. poll 大致写法: 1. poll,epoll,select系统调用的后端,都用作查询对一个或多个文件描述符的读写是否阻塞 2. unsigned int (*poll) (struct file *, struct poll_table_struct *); 3. 代码模型: static unsigned int test_poll(struct file *file, struct poll_table_struct *table) { unsigned int poll = 0; test_t *p = file->private_data; /*printk("In test_poll.\n");*/ //功能: 在设备状态变化,等待队列被唤醒时,唤醒select poll_wait(file, &p->wq, table); //如果可读 if(!p->is_empty) poll |= POLLIN | POLLRDNORM; return poll; } 八. ioctl 大致写法: 1. long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long); 2. cmd的格式: --------------------------------------- | dir 2 | size 14 | type 8 | nr 8 | ---------------------------------------- 3. 创建ioctl命令的宏: #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) #define _IOR(type,nr,datatype) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(datatype))) #define _IOW(type,nr,datatype) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(datatype))) #define _IOWR(type,nr,datatype) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(datatype))) 4. 代码模型: #define __SET_GET_FOO__ #ifndef __SET_GET_FOO__ #define SET_FOO _IOW(TYPE, NR, int) #define GET_FOO _IOR(TYPE, NR, int) #else #define SET_FOO _IOC(_IOC_WRITE, TYPE, NR, sizeof(int)) #define GET_FOO _IOC(_IOC_READ, TYPE, NR, sizeof(int)) #endif static long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret; switch(cmd) { case SET_FOO: ret = copy_from_user(&num, (void *)arg, sizeof(int)); if(ret) return -EFAULT; break; case GET_FOO: ret = copy_to_user((void *)arg, &num, sizeof(int)); if(ret) return -EFAULT; break; default: return -EINVAL; } return 0; } 九. close 大致写法: 1. int (*release) (struct inode *, struct file *); 2. 代码模型: static int test_close(struct inode *inode, struct file *file) { return 0; } 十. fasync 大致写法: 1. int (*fasync) (int, struct file *, int); 2. 前提条件: 1. 应用层(app)设置fd的owner: fcntl(fd, F_SETOWN, getpid()); //设置fd的owner 2. fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_ASYNC); //设置O_ASYNC标志 3. 当使用fcntl()设置O_ASYNC时,会调用fasync()函数. 4. 代码模型: static int test_fasync(int fd, struct file *file, int on) { test_t *p = file->private_data; printk("In test_fasync: fd = %d, on = %d\n", fd, on); // fasync_helper自己会去申请结构体struct fasync_struct的空间,并赋值 // 所以在这里我们只需要传入指向这个结构体的指针就行了 return fasync_helper(fd, file, on, &p->fa); } 5. 在设备状态变化时发送信号给使用异步通知的进程: kill_fasync(&p->fa, SIGIO, POLL_IN); 十一. 等待队列API: 1. 定义: wait_queue_head_t wq; 2. 初始化: init_waitqueue_head(&wq); 3. 两种把当前进程加入到等待队列睡眠: 1. wait_event(wq, cond); //深度睡眠,top命令下状态标志: D 2. wait_event_interruptible(wq, cond); //可被信号中断睡眠,top命令下状态标志: S 4. 两种唤醒等待队列: 1. wake_up(&wq); //唤醒深度睡眠,普通睡眠:D,S 2. wake_up_interruptible(&wq); //唤醒普通睡眠:S 十二. 驱动wait_queue poll fasync: cat > test.c << EOF #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/sched.h> #include <linux/poll.h> //设备名,可以通过命令: cat /proc/devices #define DEV_NAME "test" struct test_s { struct file_operations fops; wait_queue_head_t wq; struct fasync_struct *fa; int major; bool is_empty; }; typedef struct test_s test_t; static int test_open(struct inode *inode, struct file *file) { //把驱动私有数据放到file的private_data中 test_t *p; p = container_of(file->f_op, test_t, fops); file->private_data = p; return 0; } static int test_close(struct inode *inode, struct file *file) { return 0; } static ssize_t test_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { test_t *p = file->private_data; /** * 修正能够输出的数据长度,在本Demo中没有用到. * if(count > BUF_SIZE - *pos) * count = BUF_SIZE - *pos; */ while(p->is_empty) { //判断当前打开文件方式是不是非阻塞方式打开的 if(file->f_flags & O_NONBLOCK) return -EAGAIN; //如果没有数据,那么就进入睡眠状态,等待被唤醒,同时要求条件为真: !p->is_empty if(wait_event_interruptible(p->wq, !p->is_empty)) return -ERESTARTSYS; } printk("Read data.\n"); p->is_empty = true; //数据读完,重新置为空 return count; //返回数据的个数 } /** * 调用这个方法请使用命令: echo 123456 > /dev/test0 */ static ssize_t test_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { test_t *p = file->private_data; p->is_empty = false; //将状态置为有数据 printk("Write data.\n"); /** * 因为下面的kill_fasync()能唤醒可中断睡眠,所以可以不需要这行代码 */ //wake_up_interruptible(&p->wq); //唤醒等待队列中的进程 kill_fasync(&p->fa, SIGIO, POLL_IN); //发送SIGIO信号量,并且表示数据可读 return count; } static unsigned int test_poll(struct file *file, struct poll_table_struct *table) { unsigned int poll = 0; test_t *p = file->private_data; /*printk("In test_poll.\n");*/ /** * 功能: 在设备状态变化,等待队列被唤醒时,唤醒select * poll()可被调用多次,也导致poll_wait()被注册多次,不过没事,正常现象 */ poll_wait(file, &p->wq, table); //如果可读,将状态返回 if(!p->is_empty) poll |= POLLIN | POLLRDNORM; return poll; } static int test_fasync(int fd, struct file *file, int on) { test_t *p = file->private_data; printk("In test_fasync: fd = %d, on = %d\n", fd, on); //注册异步事件 return fasync_helper(fd, file, on, &p->fa); } struct test_s test = { .fops = { .owner = THIS_MODULE, .open = test_open, .release = test_close, .read = test_read, .write = test_write, .poll = test_poll, .fasync = test_fasync, }, .major = 0, .is_empty = true, }; int __init test_init(void) { int ret; // 初始化等待队列 init_waitqueue_head(&test.wq); // 注册字符设备驱动 ret = register_chrdev(test.major, DEV_NAME, &test.fops); if(ret > 0) { test.major = ret; printk("major = %d\n", test.major); ret = 0; } return ret; } void __exit test_exit(void) { // 卸载字符设备驱动 unregister_chrdev(test.major, DEV_NAME); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); EOF 十三. 应用wait_queue poll fasync: cat > app.c << EOF #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> int fd; void sig_handler(int sig) { int ret; char buf[1024]; if(sig == SIGIO) printf("got a signal SIGIO\n"); /** * ret = read(fd, buf, sizeof(buf)); * if(-1 == ret) * perror("read"); */ } int main(int argc, char **argv) { int ret; fd_set fds; char buf[1024] = "hello"; signal(SIGIO, sig_handler); fd = open(argv[1], O_RDWR); if(-1 == fd) { perror("open"); exit(1); } fcntl(fd, F_SETOWN, getpid()); //设置fd的owner fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_ASYNC); //设置O_ASYNC标志 while(1) { ret = read(fd, buf, 1024); if (-1 == ret ) { perror("read"); } } close(fd); return 0; } EOF