从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知
文章目录
1. poll简介
阻塞型IO相对于非阻塞型IO来说,最大的有点是资源不可用是进程主动放弃CPU让其他的进程运行,而不用不停的轮询,提高系统的效率,但是缺点也是比较明显的就是进程阻塞之后不能做其他的事情,这在一个进程要同时对多个设备进行操作时非常不便。解决这个问题的方法有很多,比如多进程、多线程和I/O多路复用,I/O复用有select、poll和Linux所持有的epoll三种方式,select、poll和epoll可以用于处理轮询,应用程序通过 select、epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用 select、epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。本节继续说明poll的用法。
在单个线程中,select 函数能够监视的文件描述符数量有最大的限制,一般为 1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用 poll 函数,poll 函数本质上和 select 没有太大的差别,但是poll 函数没有最大文件描述符限制。
2. poll函数接口
2.1 应用层poll接口
应用层poll函数的原型如下:
原 型: int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功 能:
@param1: 要监视的文件描述符集合以及要监视的事件为一个数组,数组元素都是结构体 pollfd类型的,pollfd 结构体如下所示:
struct pollfd {
int fd; // 文件描述符,如果fd无效的话那么events监视事件也就无效,并且revents返回0
short events; // 请求的事件
short revents; // 返回的事件,由Linux 内核设置具体的返回事件
};
可监视的事件类型如下所示:
POLLIN // 有数据可以读取。
POLLPRI // 有紧急的数据需要读取。
POLLOUT // 可以写数据。
POLLERR // 指定的文件描述符发生错误。
POLLHUP // 指定的文件描述符挂起。
POLLNVAL // 无效的请求。
POLLRDNORM // 等同于 POLLIN
@param2: poll 函数要监视的文件描述符数量
@param3: 超时时间,单位为ms
@return: 返回revents域中不为0的pollfd结构体个数,也就是发生事件或错误的文件描述符数量;
0表示超时;
-1发生错误并设置errno为错误类型
2.2 驱动中poll接口
在驱动程序的 poll 函数中调用 poll_wait 函数,poll_wait 函数不会引起阻塞,只是将应用程序添加到 poll_table 中,poll_wait 函数原型如下:
原 型: void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
功 能: 将应用程序添加到poll_table中
@param1: 打开的设备文件
@param2: 要添加到poll_table中的等待队列头
@param3: poll_table指针,就是file_operations 中 poll 函数的 wait 参数
@return: 无返回值
3. 示例代码
3.1 demo.c
在 vser_poll 函数中,调用 poll_wait 函数将应用程序添加到 poll_table 中,如果监听的设备(虚拟串口设备)发生事件,FIFO不为空的时候返回POLLIN表示设备可读,然后应用程序中会依据返回的值进行判断,执行对应的操作。
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/kfifo.h>
#include <linux/wait.h>
#include <linux/poll.h>
#define VSER_CHRDEV_ANME ("vser_chrdev")
#define VSER_CLASS_ANME ("vser_cls")
#define VSER_DEVICE_ANME ("vser_dev_28")
#define KFIFO_SIZE (16)
#define FLAG (0)
struct vser_dev{
dev_t dev_no;
int major;
int minor;
struct cdev cdev;
struct class *cls;
struct device *dev;
wait_queue_head_t rwqh; // 定义读的等待队列头
wait_queue_head_t wwqh; // 定义写的等待队列头
};
struct vser_dev test_vser_dev;
DEFINE_KFIFO(vser_fifo, char, KFIFO_SIZE); // 声明定义一个虚拟串口
static int vser_open(struct inode *inode, struct file *filp)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
filp->private_data = &test_vser_dev;
return 0;
}
static int vser_release(struct inode *indoe, struct file *filp)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
unsigned int copied_num, ret;
struct vser_dev *test_vser_dev = filp->private_data;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
if ( kfifo_is_empty(&vser_fifo) ) // kfifo为空返回真
{
printk("kfifo_is_empty.\n");
if ( filp->f_flags & O_NONBLOCK ) // 如果非阻塞方式打开直接返回
{
printk("O_NONBLOCK.\n");
ret = -EAGAIN; // try again
}
// condition 条件不成立的时候进程休眠,即kfifo为空时进程休眠
if ( wait_event_interruptible(test_vser_dev->rwqh, !kfifo_is_empty(&vser_fifo)) < 0 )
{
printk("line num = %d.\n", __LINE__);
ret = -ERESTARTSYS;
}
goto err0;
}
printk("kfifo is not empty.\n");
if (size > KFIFO_SIZE)
{
size = KFIFO_SIZE; // 判断拷贝内容的大小
}
ret = kfifo_to_user(&vser_fifo, userbuf, size, &copied_num); // kfifo不为空将数据拷贝到用户空间
if (ret < 0)
{
printk("kfifo_to_user failed.\n");
ret = -EFAULT; // Bad Address
goto err0;
}
printk("%s copied_num = %d.\n", __FUNCTION__, copied_num);
if ( !kfifo_is_full(&vser_fifo) ) // kfifo不为满
{
printk("line num = %d.\n", __LINE__);
wake_up_interruptible(&test_vser_dev->wwqh); // 唤醒写的等待队列头
}
return copied_num;
err0:
return ret;
}
static ssize_t vser_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
unsigned int copied_num = 0;
unsigned int ret = 0;
struct vser_dev *test_vser_dev = filp->private_data;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
if ( kfifo_is_full(&vser_fifo) ) // kfifo为满返回真
{
printk("kfifo_is_full.\n");
if ( filp->f_flags & O_NONBLOCK ) // 判断是否以非阻塞方式打开
{
printk("%s -- O_NONBLOCK.\n", __FUNCTION__);
ret = -EAGAIN;
goto err0;
}
if (wait_event_interruptible(test_vser_dev->wwqh, !kfifo_is_full(&vser_fifo)) < 0)
{
printk("line num = %d.\n", __LINE__);
ret = -ERESTARTSYS;
}
goto err0;
}
if (size > KFIFO_SIZE)
{
size = KFIFO_SIZE;
}
ret = kfifo_from_user(&vser_fifo, userbuf, size, &copied_num); // kfifo不为满,则将用户空间数据拷贝到内核空间
if (ret == -EFAULT)
{
printk("kfifo_from_user failed.\n");
goto err0;
}
printk("%s -- copied_num = %d.\n", __FUNCTION__, copied_num);
if ( !kfifo_is_empty(&vser_fifo) )
{
printk("line num = %d.\n", __LINE__);
wake_up_interruptible(&test_vser_dev->rwqh); // 唤醒读的等待队列
}
return copied_num;
err0:
return ret;
}
unsigned int vser_poll(struct file *filp, struct poll_table_struct *wait)
{
struct vser_dev *test_vser_dev = filp->private_data;
unsigned int mask = 0;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
poll_wait(filp, &test_vser_dev->rwqh, wait);
poll_wait(filp, &test_vser_dev->wwqh, wait);
if ( !kfifo_is_empty(&vser_fifo) )
{
printk("line num = %d.\n", __LINE__);
mask |= (POLLIN | POLLRDNORM);
}
return mask;
}
struct file_operations vser_fops =
{
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
.poll = vser_poll,
};
static int __init vser_init(void)
{
int ret;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
if (test_vser_dev.major)
{
test_vser_dev.dev_no = MKDEV(test_vser_dev.major, 0);
ret = register_chrdev_region(test_vser_dev.dev_no, 1, VSER_CHRDEV_ANME);
if (ret < 0)
{
printk("register_chrdev_region failed.\n");
goto register_chrdev_region_err;
}
}
else
{
ret = alloc_chrdev_region(&test_vser_dev.dev_no, 0, 1, VSER_CHRDEV_ANME);
if (ret < 0)
{
printk("alloc_chrdev_region failed.\n");
goto alloc_chrdev_region_err;
}
}
cdev_init(&test_vser_dev.cdev, &vser_fops);
ret = cdev_add(&test_vser_dev.cdev, test_vser_dev.dev_no, 1);
if (ret < 0)
{
printk("cdev_add failed.\n");
goto cdev_add_err;
}
test_vser_dev.cls = class_create(THIS_MODULE, VSER_CLASS_ANME);
if ( IS_ERR(test_vser_dev.cls) )
{
printk("class_create failed.\n");
ret = PTR_ERR(test_vser_dev.cls);
goto class_create_err;
}
test_vser_dev.dev = device_create(test_vser_dev.cls, NULL, test_vser_dev.dev_no, NULL, VSER_DEVICE_ANME);
if ( IS_ERR(test_vser_dev.dev) )
{
printk("device_create failed.\n");
ret = PTR_ERR(test_vser_dev.dev);
goto device_create_err;
}
init_waitqueue_head(&test_vser_dev.rwqh); // 初始化读的等待队列头
init_waitqueue_head(&test_vser_dev.wwqh); // 初始化写的等待队列头
return 0;
device_create_err:
class_destroy(test_vser_dev.cls);
class_create_err:
cdev_del(&test_vser_dev.cdev);
cdev_add_err:
unregister_chrdev(test_vser_dev.major, VSER_CHRDEV_ANME);
alloc_chrdev_region_err:
register_chrdev_region_err:
return ret;
}
static void __exit vser_exit(void)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
device_destroy(test_vser_dev.cls, test_vser_dev.dev_no);
class_destroy(test_vser_dev.cls);
cdev_del(&test_vser_dev.cdev);
unregister_chrdev(test_vser_dev.major, VSER_CHRDEV_ANME);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
3.2 test.c
test.c 是测试程序,在19行定义了 pollfd 结构体的数组,并将其请求的事件定义为POLLIN(代码25和35行),返回的时间初始化为0(代码26行和36行)。
在代码40行调用了poll函数,其中参数一要比fd的最大值大1,监听两个设备,超时时间设置为10s。在代码的44行和61行分别判断是那个设备产生的时间然后进行对应的操作
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#define TIMEOUT (1000*10) // 超时时间 10s
#define DEVICE_NUM (2)
int main(int argc, char *argv[])
{
int ret; // 要监视的文件描述符
char r_buf[16];
struct pollfd fds[DEVICE_NUM]; // 定义pollfd结构体,要监视的文件描述集的集合
// 打开自己定义的设备
fds[0].fd = open("/dev/vser_dev_28", O_RDWR | O_NONBLOCK);
if (fds[0].fd < 0)
{
perror("open");
return -1;
}
fds[0].events = POLLIN; // 监视设备是否可以读取
fds[0].revents = 0;
// 打开触摸屏设备
fds[1].fd = open("/dev/input/touchscreen0", O_RDWR | O_NONBLOCK);
if (fds[1].fd < 0)
{
perror("open");
return -1;
}
fds[1].events = POLLIN; // 监视设备是否可读
fds[1].revents = 0;
while(1)
{
ret = poll(fds, DEVICE_NUM, TIMEOUT); // 超时时间10s
printf("ret = %d\n", ret);
if (ret > 0)
{
if (fds[0].revents & POLLIN)
{
ret = read(fds[0].fd, r_buf, sizeof(r_buf));
if (ret > 0) // 读到内容
{
printf("r_buf = %s\n", r_buf);
}
else if (ret == 0) // 读到文件末尾
{
printf("read end of file\n");
printf("r_buf = %s\n", r_buf);
}
else // 读取失败
{
perror("read");
}
}
if (fds[1].revents & POLLIN)
{
printf("touch screen\n");
}
}
else if (ret == 0) // 超时
{
printf("timeout\n");
}
else // 出错
{
perror("poll");
}
}
close(fds[0].fd);
close(fds[1].fd);
return 0;
}
3.3 Makefile
KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)
EXEC = app
OBJS = test.o
CC = arm-linux-gnueabihf-gcc
$(EXEC):$(OBJS)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
$(CC) $^ -o $@
.o:.c
$(CC) -c $<
install:
sudo cp *.ko /tftpboot
sudo cp app /tftpboot
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
clean:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
obj-m += demo.o
3.4 测试结果
root@am335x-evm:~# insmod demo.ko // 加载模块
[ 139.768133] vser_init -- 184.
root@am335x-evm:~# ./app & // 后台运行应用程序
[1] 873
root@am335x-evm:~# [ 143.445605] vser_open -- 34.
[ 143.449182] vser_poll -- 157. // 应用层的poll调用驱动层的vser_poll函数
[ 153.459194] vser_poll -- 157.
ret = 0 // 在10s内没有向FIFO中写入任何数据,返回poll超时
timeout
[ 153.464956] vser_poll -- 157.
// 敲回车键,显示出命令行,往FIFO中写入数据
root@am335x-evm:~# echo "123" > /dev/vser_dev_28 // 往FIFO中写入数据
[ 160.976397] vser_open -- 34.
[ 160.979752] vser_write -- 107. // 调用驱动中的write函数
[ 160.982988] vser_write -- copied_num = 4.
[ 160.992330] line num = 142.
[ 160.998717] vser_poll -- 157.
[ 161.001746] line num = 164. // FIFO不为空,返回POLLIN
ret = 1
[ 161.004885] vser_read -- 53. // 监视的fds[0].revents返回POLLIN,调用read函数
root@am335x-evm:~# [ 161.020793] vser_release -- 43.
[ 161.035843] kfifo is not empty.
[ 161.041620] vser_read copied_num = 4.
[ 161.045340] line num = 91. // 将内核空间数据拷贝到用户空间
r_buf = 123 // 打印读取到的数据
[ 161.056083] vser_poll -- 157.
[ 171.066131] vser_poll -- 157.
ret = 0 // 超时
[ 171.069562] vser_poll -- 157.
timeout
[ 181.079602] vser_poll -- 157.
ret = 0
[ 181.082982] vser_poll -- 157.
timeout
root@am335x-evm:~# echo "789" > /dev/vser_dev_28
[ 190.720722] vser_open -- 34.
[ 190.724107] vser_write -- 107.
[ 190.733238] vser_write -- copied_num = 4.
[ 190.744285] line num = 142.
[ 190.749471] vser_poll -- 157.
[ 190.752500] line num = 164.
ret = 1
[ 190.755543] vser_read -- 53.
root@am335x-evm:~# [ 190.771853] vser_release -- 43.
[ 190.786381] kfifo is not empty.
[ 190.794032] vser_read copied_num = 4.
[ 190.801033] line num = 91.
r_buf = 789
[ 190.804055] vser_poll -- 157.
root@am335x-evm:~# ps -aux // 查看后台运行的进程
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 2.7 1.5 5232 3932 ? Ss 20:20 0:05 /sbin/init
root 2 0.0 0.0 0 0 ? S 20:20 0:00 [kthreadd]
... ...
root 873 0.1 0.3 1340 876 ttyS0 S 20:22 0:00 ./app
root 883 0.5 0.4 1812 1192 ? Ss 20:23 0:00 /sbin/agetty -8 -L ttyS
root 884 0.0 0.5 2636 1276 ttyS0 R+ 20:23 0:00 ps -aux
root@am335x-evm:~# kill -9 873 // 杀死./app进程
[ 206.857174] vser_poll -- 157.
root@am335x-evm:~# [ 206.886797] vser_release -- 43.
[1]+ Killed ./app
root@am335x-evm:~# rmmod demo.ko // 卸载模块
[ 210.412966] vser_exit -- 248.