/***********************************************************************************
*
* 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