從內核中最簡單的驅動程序入手,描述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.