最近在分析poll系统调用的实现过程, 写了一个测试例子, 记录下;
首先写一个驱动, 分配一块内存, 将其模拟为硬件;
创建一个sysfs文件节点, 通过其对这块内存写入, 模拟硬件产生数据;
再写一个应用, 监测这块内存, 当里面有数据时, 自动调用驱动的读函数,
驱动:
//poll_driver_test.c
/* 内核维护一个环形缓冲区, 对其有两个操作, 写和读:
* 写: 当写入'p', 唤醒等待队列, app开始读;
* 读: 读取缓冲区的数据;
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#define __here__ printk("--->%s, %s, %d;\n", __FILE__, __func__, __LINE__);
#define N_TTY_BUF_SIZE 32 //注意缓冲区大小必须为2次幂, 这样才能截断;
#define MIN(x, y) (x) > (y) ? (y) : (x);
static int major = 99; /* 主设备号, 用于区分设备类 */
static int minor = 0; /* 次设备号, 用于区分哪个设备 */
static dev_t devno;
static struct cdev cdev1;
struct class *rat_class;
static struct device *dev1 = NULL;
static char buf[N_TTY_BUF_SIZE]; //模拟的串口缓冲区;
static char ss[1024]; //模拟的用户空间;
static char *ps;
static int i;
/* read: 放入缓冲区的地址, 没有截断;
* tail: 取出缓冲区的地址, 被截断;
* read_tail: 取出缓冲区的地址, 没有截断;
*/
static int read = 0, tail = 0, read_tail = 0;
static wait_queue_head_t rat_wait;
static struct kobject *kot;
static bool wakeup = false;
//写入数据:
static void put_buffer(unsigned char c, char *bot)
{
bot[read & (N_TTY_BUF_SIZE - 1)] = c;
read++;
}
//取出数据:
static int get_buffer(void)
{
int n = 0;
tail = read_tail & (N_TTY_BUF_SIZE - 1);
n = MIN(read - read_tail, N_TTY_BUF_SIZE - tail);
if (n) {
memcpy(ps, &buf[tail], n);
ps += n;
read_tail += n;
}
return n;
}
static int rat_open(struct inode *inodep, struct file *filep)
{
__here__;
return 0;
}
static ssize_t rat_read (struct file *filp, char __user *buf, size_t count, loff_t *lof)
{
int nua, nub, error;
memset(ss, 0, 1024);
ps = ss;
nua = get_buffer();
nub = get_buffer();
copy_to_user(buf, ss, nua + nub);
return nua + nub;
}
static ssize_t rat_write (struct file *filp, const char __user *buf, size_t count, loff_t *lof)
{
__here__;
return 0;
}
static unsigned int rat_poll (struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &rat_wait, wait);
if (wakeup) {
mask |= POLLIN | POLLRDNORM;
wakeup = false;
}
return mask;
}
static int rat_fasync (int fa, struct file *filp, int fb)
{
__here__;
return 0;
}
static int rat_release (struct inode *inode, struct file *filp)
{
__here__;
return 0;
}
static struct file_operations rat_ops = {
.owner = THIS_MODULE,
.open = rat_open,
.read = rat_read,
.write = rat_write,
.poll = rat_poll,
.fasync = rat_fasync,
.release = rat_release,
};
static ssize_t wt_show(struct device *dev, struct device_attribute *attr, char *buf)
{
__here__;
return 1;
}
static ssize_t wt_store(struct device *dev, struct device_attribute *attr,
const char *s1, size_t count)
{
bool toen = false;
printk("--->%s, %d, s1: %s, count: %d, toen: %d;\n",
__func__, __LINE__, s1, count, toen);
if (*s1 == '\n')
return 1;
for (i = 0; i < strlen(s1); i++) {
put_buffer(s1[i], buf);
if (s1[i] == 'p')
toen = true;
}
if (toen) {
wake_up_interruptible(&rat_wait);
wakeup = true;
}
return count;
}
//生成文件: /sys/wta/wtc;
struct device_attribute dev_attr_wtb = {
.attr = {.name = "wtc", .mode = 0777 },
.show = wt_show,
.store = wt_store,
};
static void mk_syf_file(void)
{
kot = kobject_create_and_add("wta", NULL);
sysfs_create_file(kot, &dev_attr_wtb.attr);
}
static int rat_init(void)
{
int ret;
printk(KERN_ALERT "rat_init\n");
/* 第一步: 登记设备区间 */
if (0 == major){
ret = alloc_chrdev_region(&devno, 0, 1, "rat"); /* cat /proc/devices能看到 */
major = MAJOR(devno);
}
else{ /* 已有主设备号 */
devno = MKDEV(major, minor);
ret = register_chrdev_region(devno, 1, "rat");
if (ret < 0){
printk(KERN_ERR "my register_chrdev_region fail.\n");
return ret;
}
}
printk(KERN_INFO "register_chrdev_region ok.\n");
/* 第二步: 注册字符设备驱动 */
cdev_init(&cdev1, &rat_ops); /* 确定f_ops */
ret = cdev_add(&cdev1, devno, 1); /* 确定devno */
if (ret < 0){
printk(KERN_ERR "Uable to add dev\n");
return ret;
}
printk(KERN_INFO "cdev_add success\n");
rat_class = class_create(THIS_MODULE, "rat");
dev1 = device_create(rat_class, NULL, devno, NULL, "my_rat"); /* 生成: /dev/my_rat */
printk(KERN_INFO "device created success\n");
/* 定义一个等待队列 */
init_waitqueue_head(&rat_wait);
/* 创建sysfs文件节点 */
mk_syf_file();
return 0;
}
static void rat_exit(void)
{
cdev_del(&cdev1);
unregister_chrdev_region(devno, 1);
printk(KERN_ALERT "rat_exit\n");
}
module_init(rat_init);
module_exit(rat_exit);
MODULE_LICENSE("GPL");
应用:
//poll_app_test.c
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
//#define __here__ printf("--->%s, %s, %d;\n", __FILE__, __func__, __LINE__);
#define __here__ do{}while(0);
static char buf[128];
int main(int argc, char **argv)
{
int fd;
char* filename = "/dev/my_rat";
unsigned char key_val;
int ret;
struct pollfd *key_fds = NULL;
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("error, can't open %s\n", filename);
return 0;
}
if(argc !=1){
printf("Usage : %s ",argv[0]);
return 0;
}
key_fds = (struct pollfd *)malloc(sizeof(*key_fds));
key_fds->fd = fd;
key_fds->events = POLLIN; //poll直接返回需要的条件;
while(1) {
ret = poll(key_fds, 1, 6000);
if(!ret){
printf("time out\n");
}
else{
if(key_fds->revents == POLLIN){
memset(buf, 0, 128);
read(fd, buf, 128);
printf("--->%s, %d, buf: %s\n", __func__, __LINE__, buf);
}
}
}
return 0;
}
终端1:
# echo addp > /sys/wta/wtc
[ 26.339663] —>wt_store, 154, s1: addp
[ 26.339663] , count: 5, toen: 0;
终端2:
# ./app
—>main, 48, buf: addp
需要注意的是,在驱动里,poll_wait()并不会阻塞进程,尽管其名字中有wait, 很容易将其功能理解为和 wait_event() 一样;
wake_up_interruptible()唤醒进程后,会回调字符操作函数集的poll(), 这个过程还不清楚怎么来的;