1,文件io模型: 阻塞, 非阻塞, 多路复用
2,文件io中的ioctl和mmap接口
3, 中断下半部:softirq, tasklet, workqueue
4, 驱动中出错判断(驱动编写的规范)
----------------------------------------------
非阻塞: 因为linux文件io都是阻塞, 需要非阻塞的话,就需要进行
应用:修改标志位
方法1:
int fd = open("/dev/xxx", O_RDWR|O_NONBLOCK);
read(fd, buf, size); <==非阻塞
方法2 :
//int fd = open("/dev/xxx", O_RDWR);
int fd = socket(AF_INET, SOCK_STREAM, 0);
int flag = fcntl(fd, F_GETFL, 0);
flag |= NONBOLOCK
fcntl(fd, F_SETFL, flag);
read(fd, buf, size); <==非阻塞
----------------------------------------------
驱动:
xxx_read()
{
//区分当前是阻塞还是非阻塞
if((filp->f_flags & O_NONBLOCK) && !have_data)
{
return -EAGAIN;
}
}
===============================================================
多路复用:监控多个文件的io操作:读,写, 出错, 会利用select/poll来实现
select应用:
int fd1<==3, int fd2 <==4;
int max = fd2 + 1;
fd_set rd_set;
whlie(1)
{
FD_ZERO(&rd_set);
FD_SET(fd1, &rd_set);
FD_SET(fd2, &rd_set);
ret = select(max, &rd_set, NULL, NULL, NULL);
if(ret > 0)
{
//判断是谁有数据
if(FD_ISSET(fd1, &rd_set))
{
//拷贝数据--此处不会阻塞
read()
}
if(FD_ISSET(fd2, &rd_set))
{
//拷贝数据--此处不会阻塞
read()
}
}
}
poll :
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; //你需要监控文件描述符
short events; //你需要监控的事件: 读(POLLIN),写(POLLOUT),出错(POLLERR)
short revents; /* returned events */ //用于检测是否发生了事件
//(如果有数据,内核会自动将POLLIN/POLLOUT|POLLERR设置到该变量上)
};
例子:
struct pollfd pfd[2];
pfd[0].fd = fd; //自己写的按键设备
pfd[0].events = POLLIN; //监控读 (POLLIN|POLLOUT|POLLERR)
pfd[1].fd = 0; //监控标准输入
pfd[1].events = POLLIN;
ret = poll(pfd, 2, -1);
//判断是哪个有数据
if(pfd[0].revents & POLLIN)
{
//拷贝数据--此处不会阻塞
read()
}
if(pfd[1].revents & POLLIN)
{
//拷贝数据--此处不会阻塞
read()
}
-----------------------------------------------------
应用:
select/poll
-----------------------------------------
驱动:
xxx_poll
{
//返回一个整数: 没有数据的时候返回0, 有数据的时候,返回POLLIN
unsigned int mask = 0;
// 将当前的等待队列头,注册到vfs层, 注意: poll_wait该函数不会导致休眠
//参数1-文件对象--当前file
//参数2--当前的等待队列头
//参数3--当前传递过来的第三个参数
poll_wait(filp, &wq_head, pts);
if(have_data)
mask |= POLLIN;
return mask;
}
poll_wait()是用在select系统调用中的.
一般你的代码会有一个struct file_operations结构,
其中fop->poll函数指针指向一个你自己的函数,
在这个函数里应该调用poll_wait()
当用户调用select系统调用时,select系统调用会
先调用
poll_initwait(&table);
然后调用你的
fop->poll();
从而将current加到某个等待队列(这里调用poll_wait()),
并检查是否有效 //这里会做判断,实际上poll_wait函数本身没有休眠功能
如果无效就调用
schedule_timeout();
去睡眠.
事件发生后,schedule_timeout()回来,调用
fop->poll();
检查到可以运行,就调用
poll_freewait(&table);
从而完成select系统调用.
重要的是fop->poll()里面要检查是否就绪,
如果是,要返回相应标志
参见内核的函数:
fs/select.c/do_select()
引用: https://blog.csdn.net/gtkknd/article/details/7229527
==============================================================
需要驱动所有按键:
struct key_desc all_keys[] = {
[0] = {
.name = "key1_up_eint0",
.irqno = IRQ_EINT(0),
.gpio = S5PV210_GPH0(0),
.code = KEY_UP,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[1] = {
.name = "key2_down_eint1",
.irqno = IRQ_EINT(1),
.gpio = S5PV210_GPH0(1),
.code = KEY_DOWN,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[2] = {
.name = "key3_left_eint2",
.irqno = IRQ_EINT(2),
.gpio = S5PV210_GPH0(2),
.code = KEY_LEFT,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[3] = {
.name = "key4_right_eint3",
.irqno = IRQ_EINT(3),
.gpio = S5PV210_GPH0(3),
.code = KEY_LEFT,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
};
//在申请中断的时候,传递不同的值
ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);
完整代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
// 设计一个对象--描述按键产生的数据包
struct key_event{
int code; //什么按键: 左键, 回车键
int value; // 按键的状态: 按下/抬起 (1/0)
};
//设计一个描述按键的对象: 名字, irqno, gpio, 按键值,触发方式
struct key_desc{
char *name;
int irqno;
int gpio;
int code;
int flags;// 触发方式
};
struct key_desc all_keys[] = {
[0] = {
.name = "key1_up_eint0",
.irqno = IRQ_EINT(0),
.gpio = S5PV210_GPH0(0),
.code = KEY_UP,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[1] = {
.name = "key2_down_eint1",
.irqno = IRQ_EINT(1),
.gpio = S5PV210_GPH0(1),
.code = KEY_DOWN,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[2] = {
.name = "key3_left_eint2",
.irqno = IRQ_EINT(2),
.gpio = S5PV210_GPH0(2),
.code = KEY_LEFT,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[3] = {
.name = "key4_right_eint3",
.irqno = IRQ_EINT(3),
.gpio = S5PV210_GPH0(3),
.code = KEY_LEFT,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
};
static int key_major = 0;
static struct class *key_cls;
static int testdata = 0x12345;
static wait_queue_head_t wq_head;
static int have_data; //表示当前是否有按键标志,
//相当于一个数据包--给用户的
static struct key_event event;
int key_drv_open (struct inode *inode, struct file *filp)
{
printk("--------^_* %s-------\n", __FUNCTION__);
// 通过文件路径可以得到inode
// 通过得到次设备号可以区分不同的设备
//int major = MAJOR(filp->f_path.dentry->d_inode->i_rdev);
int major = imajor(filp->f_path.dentry->d_inode);
int major2 = imajor(inode);
int minor = iminor(filp->f_path.dentry->d_inode);
int minor2 = iminor(inode);
printk("major = %d, minor = %d\n", major, minor);
printk("major2 = %d, minor2 = %d\n", major2, minor2);
memset(&event, 0, sizeof(struct key_event));
have_data = 0; //为假--一开始都没有按键按下或者抬起
return 0;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret;
//区分当前是阻塞还是非阻塞
if((filp->f_flags & O_NONBLOCK) && !have_data)
{
return -EAGAIN;
}
// 在资源不可达的时候,进行休眠
// 参数1---当前驱动中的等待队列头
// 参数2--休眠的条件: 假的话就休眠,真就不休眠
wait_event_interruptible(wq_head, have_data);
ret = copy_to_user(buf, &event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
//清零,以备下次充值
memset(&event, 0, sizeof(struct key_event));
have_data = 0; //没有数据了,等待下一次数据
return count;
}
int key_drv_close(struct inode *inode, struct file *filp)
{
printk("--------^_* %s-------\n", __FUNCTION__);
return 0;
}
unsigned int key_drv_poll (struct file *filp, struct poll_table_struct *pts)
{
//返回一个整数: 没有数据的时候返回0, 有数据的时候,返回POLLIN
unsigned int mask = 0;
// 将当前的等待队列头,注册到vfs层, 注意: poll_wait该函数不会导致休眠
//参数1-文件对象--当前file
//参数2--当前的等待队列头
//参数3--当前传递过来的第三个参数
poll_wait(filp, &wq_head, pts);
/*select
poll_initwait
poll_wait y/正常返回ret值 N/schedule_timeout(休眠)
*/
if(have_data)
mask |= POLLIN;
return mask;
}
// 4, 实现fops
const struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_drv_open,
.read = key_drv_read,
.poll = key_drv_poll,
.release =key_drv_close,
};
//中断处理方法
//参数1--当前产生的中断的号码
//参数2--request_irq传递过来的参数
irqreturn_t key_irq_handler(int irqno, void *dev_id)
{
int *p = (int *)dev_id;
//printk("-----------%s-------0x%x-----\n", __FUNCTION__, *p);
//区分当前是哪个按键
struct key_desc *pdesc = (struct key_desc *)dev_id;
//区分按下还是抬起
int value = gpio_get_value(pdesc->gpio);
if(value)
{
//抬起
printk("<kernel>--%s : release\n", pdesc->name);
//填充值
event.code = pdesc->code;
event.value = 0;
}else
{
//按下
printk("<kernel>--%s : pressed\n", pdesc->name);
event.code = pdesc->code;
event.value = 1;
}
have_data = 1;//表示有数据了
wake_up_interruptible(&wq_head);
return IRQ_HANDLED;
}
static int __init key_drv_init(void)
{
int ret;
// 1, 申请主设备号--动态申请主设备号
key_major = register_chrdev(0, "key_dev", &key_fops);
// 2, 创建设备节点
key_cls = class_create(THIS_MODULE, "key_cls");
device_create(key_cls, NULL, MKDEV(key_major, 0), NULL, "key0");
// 3, 硬件初始化-- 映射地址或者中断申请
//参数1--中断号码
//获取中断号码的方法: 1,外部中断IRQ_EINT(x)
// 2, 头文件 #include <mach/irqs.h> #include <plat/irqs.h>
//参数2--中断的处理方法
//参数3--中断触发方式: 高电平,低电平,上升沿,下降沿,双边沿
/*
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
*/
//参数4--中断的描述-自定义字符串
//参数5--传递个参数2的任意数据
//返回值0表示正确
int i;
int irqno;
int flags;
char *name;
for(i=0; i<ARRAY_SIZE(all_keys); i++)
{
name = all_keys[i].name;
irqno = all_keys[i].irqno;
flags = all_keys[i].flags;
ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);
if(ret != 0)
{
printk("request_irq error\n");
return ret;
}
}
// 定义一个等待队列头,并且初始化
init_waitqueue_head(&wq_head);
return 0;
}
static void __exit key_drv_exit(void)
{
// 释放中断
//参数1--中断号码
//参数5--和request_irq第5个参数保持一致
int i;
int irqno;
for(i=0; i<ARRAY_SIZE(all_keys); i++)
{
irqno = all_keys[i].irqno;
free_irq(irqno, &all_keys[i]);
}
device_destroy(key_cls, MKDEV(key_major, 0));
class_destroy(key_cls);
unregister_chrdev(key_major, "key_dev");
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
应用层app代码
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <poll.h>
#include <linux/input.h>
// 设计一个对象--描述按键产生的数据包
struct key_event{
int code; //什么按键: 左键, 回车键
int value; // 按键的状态: 按下/抬起 (1/0)
};
int main(int argc, char *argv[])
{
int on;
int ret;
char kbd_buf[128];
struct key_event data;
//直接将驱动模块当做文件来操作
int fd = open("/dev/key0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
struct pollfd pfd[2];
pfd[0].fd = fd; //自己写的按键设备
pfd[0].events = POLLIN; //监控读 (POLLIN|POLLOUT|POLLERR)
pfd[1].fd = 0; //监控标准输入
pfd[1].events = POLLIN;
while(1)
{
//参数1--你需要监控的文件描述符的集合
//参数2--监控的文件的个数
//参数3--监控的时间--毫秒为单位,如果是负数,永久监控
ret = poll(pfd, 2, -1);
if(ret < 0)
{
perror("poll");
exit(1);
}
if(ret > 0)
{
//判断是哪个有数据
if(pfd[1].revents & POLLIN)
{
//表示键盘是输入
ret = read(0, kbd_buf, 128);
//fgets(kbd_buf, 128, stdin);
kbd_buf[ret] = '\0';
printf("kbd_buf = %s\n", kbd_buf);
}
if(pfd[0].revents & POLLIN)
{
//获取数据--不会阻塞
ret = read(pfd[0].fd, &data, sizeof(struct key_event));
//解析包
switch(data.code)
{
case KEY_UP:
if(data.value)
{
printf("<app>---KEY_UP pressed\n");
}else
{
printf("<app>---KEY_UP release\n");
}
break;
case KEY_DOWN:
if(data.value)
{
printf("<app>---KEY_DOWN pressed\n");
}else
{
printf("<app>---KEY_DOWN release\n");
}
break;
case KEY_LEFT:
if(data.value)
{
printf("<app>---KEY_LEFT pressed\n");
}else
{
printf("<app>---KEY_LEFT release\n");
}
break;
case KEY_RIGHT:
if(data.value)
{
printf("<app>---KEY_RIGHT pressed\n");
}else
{
printf("<app>---KEY_RIGHT release\n");
}
break;
}
}
}
}
close(fd);
}
if(pfd[1].revents & POLLIN) 中的&符号为位运算,实际上POLLIN是一个宏定义
其中的数刚好可以用&运算之后做判断是否为真