目录
POLL机制
同步阻塞IO和异步阻塞IO
同步阻塞IO
异步阻塞IO
文件I/O事件类型
poll()函数
头文件
函数定义
实验环节
app.c文件
执行过程
poll函数底层机制
SYSCALL_DEFINE3函数
do_sys_poll()函数
do_poll()函数
poll函数指针
poll驱动模板
相关结构体框图
实验环节:poll实验
实验设计
dts_led.c文件
app.c文件
Makefile文件
执行过程
poll机制的底层实现原理:基于等待队列来实现。
poll()函数,poll()函数底层函数接口有一个对应的函数指针。当调用poll()函数的时候,会找到对应的file_operations的成员变量poll,最终会调用poll成员变量指向的函数指针。
应用层调用一个 read / write 对一个文件进行读写操作时, read / write可能使当前的进程或线程进入一个休眠态(进程 / 线程阻塞在一个文件的读写操作上),陷入休眠态的进程 / 线程只能通过对应的文件设备驱动唤醒自己。
在应用层调用poll()函数时,poll()函数会对多个文件进行轮询操作,去查看每一个文件是否有特定的事件。若所有文件都没有发生特定的事件,那么poll()函数会阻塞当前进程 / 线程。
poll()函数文件操作涉及多个文件的轮询,所以poll()函数引起的进程 / 线程休眠,可以会被多个设备驱动唤醒。
多个文件对应多个设备驱动。阻塞在多个文件的轮询操作上(poll),可以被多个设备驱动唤醒。
事件类型:可读、可写、异常...
poll()函数的目的是查询每个文件的I/O事件,没查到就会引起休眠。这些事件是由设备驱动产生,产生后会唤醒由poll()函数导致的进程 / 线程休眠,poll()函数也能把发生的事件返回给用户空间。
#include
/*
* 监视多个文件描述符的指定事件(注意:不是所有事件)
* 事件发生时(设备驱动唤醒poll函数导致休眠的进程/线程),把发生的具体事件通知给用户程序
* fds:数组类型详见下
* nfds:pollfd数组的元素个数,要监控的文件描述符数量
* timeout:超时时间(ms),不希望进程线程一直休眠在poll函数里
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
* 返回值:
* 成功:发生事件的文件数量,超时返回0
* 失败:-1
*/
struct pollfd
{
int fd; // 要监视的文件描述符
short events; // 指定要监视的请求事件类型,通过设置一系列的宏来描述
short revents; // 返回的事件类型,内核设置具体的返回事件,记录实际发生的事件
};
events监视的事件:
POLLIN:系统内核通知应用层指定数据已经准备好,读数据不会被阻塞
POLLPRI:有紧急的数据需要被读取
POLLOUT:系统内核通知应用层IO缓冲区已经准备好,写数据不会被阻塞
POLLERR:指定的文件描述符发生错误,必须先解决错误才能对文件做其他的处理
POLLNVAL:无效的请求
...
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
struct pollfd fds = {0};
fds.fd = 0; //标准输入
fds.events = POLLIN;
int ret = poll(&fds, 1, 5000); //监视1个数组,5s超时
if(ret == -1){
printf("poll error!\n");
}else if(ret){
printf("data is ready!\n");
}else if(ret == 0){
printf("time out!\n");
}
return 0;
}
gcc app.c -o App
sudo ./App(回车等待5s)
sudo ./App &
cat
2
// 此宏是如何一步一步推导到sys_poll见下
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,int, timeout_msecs)
{
struct timespec64 end_time, *to = NULL;
int ret;
if (timeout_msecs >= 0) {
to = &end_time;
poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
}
// 关键
ret = do_sys_poll(ufds, nfds, to);
...
return ret;
}
该函数保存在内核/fs/select.c文件中。
函数执行过程:
复制用户空间pollfd数组到内核空间
分配静态数组内存(一个poll_list结构体)
动态分配内存(一组poll_list结构体)
调用do_poll()函数
返回修改后的pollfd数组到用户空间(主要是返回修改后的revents值)
static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, struct timespec64 *end_time)
{
struct poll_wqueues table;
int err = -EFAULT, fdcount, len, size;
// 此宏定义详见下
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
struct poll_list *const head = (struct poll_list *)stack_pps;
struct poll_list *walk = head;
// 用户空间调用poll函数的第二个参数,数组长度
unsigned long todo = nfds;
...
// 获取静态分配的数组大小,取最小值
// 判断可以存放多少个用户空间传进来的pollfd结构体
len = min_t(unsigned int, nfds, N_STACK_PPS);
for (;;) {
walk->next = NULL;
walk->len = len;
if (!len)
break;
if (copy_from_user(walk->entries, ufds + nfds-todo,
sizeof(struct pollfd) * walk->len))
goto out_fds;
todo -= walk->len;
if (!todo)
break;
// 计算剩下的文件描述符所需空间大小,最大为一个页
len = min(todo, POLLFD_PER_PAGE);
size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
walk = walk->next = kmalloc(size, GFP_KERNEL);
if (!walk) {
err = -ENOMEM;
goto out_fds;
}
}
//
poll_initwait(&table);
// 详见下,fdcount为所有发生事件的文件数量
// 为了返回修改后的pollfd数组到用户空间
fdcount = do_poll(head, &table, end_time);
poll_freewait(&table);
// 在此遍历poll_list(链表)
for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j;
// 遍历每个poll_list元素的pollfd数组
for (j = 0; j < walk->len; j++, ufds++)
// 返回事件值给用户空间
if (__put_user(fds[j].revents, &ufds->revents))
goto out_fds;
}
...
}
该函数保存在内核/fs/select.c文件中。
函数里面的三重循环:
第一重:确保线程 / 进程被唤醒后,继续执行一次循环体内容
第二重:遍历一组poll_list
第三重:遍历每一组poll_list的一组pollfd
static int do_poll(struct poll_list *list, struct poll_wqueues *wait,
struct timespec64 *end_time)
{
poll_table* pt = &wait->pt;
ktime_t expire, *to = NULL;
int timed_out = 0, count = 0;
u64 slack = 0;
__poll_t busy_flag = net_busy_loop_on() ? POLL_BUSY_LOOP : 0;
unsigned long busy_start = 0;
...
for (;;) {
struct poll_list *walk;
bool can_busy_loop = false;
// 二重:遍历 poll_list 结构体(链表)
for (walk = list; walk != NULL; walk = walk->next) {
struct pollfd * pfd, * pfd_end;
pfd = walk->entries;
pfd_end = pfd + walk->len;
// 三重:遍历 pollfd 结构体(数组)
for (; pfd != pfd_end; pfd++) {
if (do_pollfd(pfd, pt, &can_busy_loop,
busy_flag)) {
// 返回值不为0,count++,表示有返回事件的文件数量
// 返回值不为0,文件没有发生任何事件
// 返回值为0,发生了某(些)事件
count++;
pt->_qproc = NULL;
/* found something, stop busy polling */
busy_flag = 0;
can_busy_loop = false;
}
}
}
pt->_qproc = NULL;
if (!count) {
count = wait->error;
// 检查当前进程或者线程是否有信号处理
if (signal_pending(current))
count = -EINTR;
}
// 下面的break会跳出最外层循环
if (count || timed_out)
break;
...
// 真正使当前进程或者线程休眠的函数
// 若timeout为1,下一次最外层循环将会从上面的break跳出
// 此函数是阻塞的,当等待的事件发生时,会从此处继续向下执行
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = 1;
}
return count;
}
// 参数2的数据类型其实是 poll_table 结构体的别名
/*
* filp:要打开的设备文件
* wait:结构图poll_table_struct类型指针
*/
__poll_t (*poll) (struct file *filp, struct poll_table_struct *wait);
//返回值:文件可用事件类型
static __poll_t xxx_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
// 详见下,注意第二个参数就是等待队列头
poll_wait(filp, &yyy, wait);
// 判断驱动程序里面发生了哪些条件,这些条件对应哪些文件的事件,设置好事件后,return回去
if(...)
{
mask |= POLLOUT | ...;
}
return mask;
}
// 参数2:等待队列头
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address)
// 其实就 上面初始化为 __pollwait 函数
// 参数2指定等待队列头
p->_qproc(filp, wait_address, p);
}
App应用程序调用poll()函数检测/dev/rgb_led设备文件的可写事件
无可写事件,进程一直休眠(poll)
有可写事件,写入字符“0”,点亮rgb红灯
file_operations->poll
监视write_data全局变量值(0-无事件发送,1-返回可写事件)
指定唤醒App进程的等待队列头(poll_wait)
file_operations->write
判断write_data全局变量值(0-点亮rgb红灯,1-唤醒poll函数引起休眠的App进程)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEV_NAME "rgb_led"
#define DEV_CNT (1)
int rgb_led_red;
int rgb_led_green;
int rgb_led_blue;
wait_queue_head_t wait_queue;
unsigned int write_data = 0;
static dev_t led_devno;
static struct cdev led_chrdev;
struct class *class_led;
struct device *device;
struct device_node *rgb_led_device_node;
static int led_chrdev_open(struct inode *inode, struct file *filp)
{
printk("open form driver\n");
return 0;
}
static ssize_t led_chrdev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret, error;
unsigned char receive_data[10]; //用于保存接收到的数据
if(cnt > 10) cnt = 10;
error = copy_from_user(receive_data, buf, cnt);
if(error < 0) return -1;
ret = kstrtoint(receive_data, 16, &write_data);
if(ret) return -1;
if(write_data){
wake_up(&wait_queue);
return cnt;
}else{
gpio_set_value(rgb_led_red, 0);
}
return cnt;
}
static int led_chrdev_release(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "finished!!!\n");
return 0;
}
__poll_t led_chrdev_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(filp, &wait_queue, wait);
if(write_data)
{
mask |= POLLOUT;
}
return mask;
}
static struct file_operations led_chrdev_fops = {
.owner = THIS_MODULE,
.open = led_chrdev_open,
.write = led_chrdev_write,
.release = led_chrdev_release,
.poll = led_chrdev_poll,
};
static int led_probe(struct platform_device *pdv)
{
int ret = -1; //保存错误状态码
unsigned int register_data = 0;
printk(KERN_ALERT "match successed!\n");
/* 获取rgb_led的设备树节点 */
rgb_led_device_node = of_find_node_by_path("/rgb_led");
if(rgb_led_device_node == NULL){
printk(KERN_ERR "get rgb_led failed!\n");
return -1;
}
/* 获取red led GPIO 引脚号 */
rgb_led_red = of_get_named_gpio(rgb_led_device_node, "rgb_led_red", 0);
if(rgb_led_red < 0){
printk(KERN_ERR "rgb_led_red failed!\n");
return -1;
}
/* 获取green led GPIO 引脚号 */
rgb_led_green = of_get_named_gpio(rgb_led_device_node, "rgb_led_green", 0);
if(rgb_led_green < 0){
printk(KERN_ERR "rgb_led_green failed!\n");
return -1;
}
/* 获取blue led GPIO 引脚号 */
rgb_led_blue = of_get_named_gpio(rgb_led_device_node, "rgb_led_blue", 0);
if(rgb_led_blue < 0){
printk(KERN_ERR "rgb_led_blue failed!\n");
return -1;
}
/* 设置GPIO为输出模式,并默认高电平 */
gpio_direction_output(rgb_led_red, 1);
gpio_direction_output(rgb_led_green, 1);
gpio_direction_output(rgb_led_blue, 1);
/* 第一步
* 采用动态分配的方式获取设备编号,次设备号为0
* 设备名称为rgb-leds,可通过命令cat /proc/devices查看
* DEV_CNT为1,当前只申请一个设备编号
*/
ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk("fail to alloc led_devno\n");
goto alloc_err;
}
/* 第二步
* 关联字符设备结构体cdev与文件操作结构体file_operations
*/
led_chrdev.owner = THIS_MODULE;
cdev_init(&led_chrdev, &led_chrdev_fops);
/* 第三步
* 添加设备到cdev_map哈希表中
*/
ret = cdev_add(&led_chrdev, led_devno, DEV_CNT);
if(ret < 0){
printk("fail to add cdev\n");
goto add_err;
}
/* 第四步:创建类 */
class_led = class_create(THIS_MODULE, DEV_NAME);
/* 第五步:创建设备 */
device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);
return 0;
alloc_err:
return -1;
add_err:
//添加设备失败时,需要注销设备号
unregister_chrdev_region(led_devno, DEV_CNT);
printk("error!\n");
}
static const struct of_device_id rgb_led[] = {
{.compatible = "fire,rgb_led"},
{/* sentinel */}
};
/* 定义平台设备结构体 */
struct platform_driver led_platform_driver = {
.probe = led_probe,
.driver = {
.name = "rgb-leds-platform",
.owner = THIS_MODULE,
.of_match_table = rgb_led,
}
};
static int __init led_platform_driver_init(void)
{
int DriverState;
init_waitqueue_head(&wait_queue);
DriverState = platform_driver_register(&led_platform_driver);
printk(KERN_ALERT "DriverState is %d\n", DriverState);
return 0;
}
static void __exit led_platform_driver_exit(void){
/* 销毁设备 */
device_destroy(class_led, led_devno);
/* 删除设备号 */
cdev_del(&led_chrdev);
/* 取消注册字符设备 */
unregister_chrdev_region(led_devno, DEV_CNT);
/* 销毁类 */
class_destroy(class_led);
platform_driver_unregister(&led_platform_driver);
printk(KERN_ALERT "led_platform_driver exit\n");
}
module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("couvrir");
MODULE_DESCRIPTION("led module");
MODULE_ALIAS("led module");
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
struct pollfd fds = {0};
int error;
if(argc != 2){
printf("commend error!\n");
return -1;
}
int fd = open("/dev/rgb_led", O_RDWR);
if(fd < 0){
printf("open file:/dev/rgb_led failed!!!\n");
return -1;
}
fds.fd = fd;
fds.events = POLLOUT;
if(poll(&fds, 1, -1) < 0) //一直休眠
printf("poll error!\n");
if(fds.revents & POLLOUT){
error = write(fd, argv[1], sizeof(argv[1]));
if(error < 0){
printf("write file error!\n");
close(fd);
}
}
error = close(fd);
if(error < 0){
printf("close file error!\n");
}
return 0;
}
照旧
虚拟机:
执行make和make copy。生成.ko文件。
开发板(在挂载目录下执行):
sudo insmod dts_led.ko
sudo ./App 0 &(设备文件没有可写事件产生,poll函数使当前App进程休眠)
sudo sh -c "echo 1 > /dev/rgb_led"(设备文件产生可写事件,App应用程序写入字符‘0’,rgb红灯被点亮)
sudo rmmod dts_led.ko