6. linux 内核事件通知驱动编程
1. eventfd 事件通知
eventfd 事件通知不仅可以用于线程间的事件通知,还可以用于内核和用户空间的事件通知。
2. eventfd 用户空间API
#include <sys/eventfd.h>
//创建事件通知句柄,可以被read 和 write 函数使用
int eventfd(unsigned int initval, int flags);
Linux shell下 输入 man eventfd
命令查看,里面有详细的使用说明和例子
3. eventfd 内核空间API
#include <linux/eventfd.h>
//将fd 转换为 struct eventfd_ctx
struct eventfd_ctx *eventfd_ctx_fdget(int fd);
//移除 struct eventfd_ctx
void eventfd_ctx_put(struct eventfd_ctx *ctx);
//相当于用户层的write
__u64 eventfd_signal(struct eventfd_ctx *ctx, __u64 n);
//相当于用户层的read
ssize_t eventfd_ctx_read(struct eventfd_ctx *ctx, int no_wait, __u64 *cnt);
4. 驱动例程
command.h
#ifndef INCLUDE_COMMAND_H
#define INCLUDE_COMMAND_H
#include <linux/ioctl.h>
//幻数
#define IOCTL_MAGIC 'x'
//定义命令
#define HELLO_RESET _IO(IOCTL_MAGIC, 1)
#define HELLO_READ _IOR(IOCTL_MAGIC, 2, int)
#define HELLO_WRITE _IOW(IOCTL_MAGIC, 3, int)
#define HELLO_NOTIFICATION _IOW(IOCTL_MAGIC, 4, int)
#endif
hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/eventfd.h>
#include <linux/timer.h>
#include "command.h"
#define HELLO_CNT 1
//主设备号为0,表示动态分配设备号
dev_t dev = 0;
static int major = 0;
static int minor = 0;
static struct cdev *hello_cdev[HELLO_CNT];
static struct class *hello_class = NULL;
static struct class_device * hello_class_dev[HELLO_CNT];
static int s_val = 0;
static int s_EventFd = -1;
static struct eventfd_ctx * eve_ctx = NULL;
static struct timer_list tm;
int hello_open(struct inode * pnode, struct file * pfile)
{
printk("open file..\n");
int num = MINOR(pnode->i_rdev);
if(num >= HELLO_CNT)
{
return -ENODEV;
}
pfile->private_data = hello_cdev[num];
return 0;
}
int hello_release(struct inode *pnode, struct file *pfile)
{
printk("release file ..\n");
return 0;
}
long hello_ioctl(struct file *pfile, unsigned int cmd, unsigned long val)
{
int ret = 0;
switch(cmd)
{
case HELLO_RESET:
{
printk("Rev HELLO_RESET cmd\n");
break;
}
case HELLO_READ:
{
printk("Rec HELLO_READ cmd\n");
ret = copy_to_user(val, &s_val, sizeof(int));
break;
}
case HELLO_WRITE:
{
printk("Rec HELLO_WRITE cmd\n");
ret = copy_from_user(&s_val, val, sizeof(int));
break;
}
case HELLO_NOTIFICATION:
{
printk("Rec HELLO_NOTIFICATION cmd..\n");
ret = copy_from_user(&s_EventFd, val, sizeof(int));
eve_ctx = eventfd_ctx_fdget(s_EventFd);
break;
}
default:
{
printk("unkownd cmd...\n");
return -EINVAL;
}
}
return ret;
}
//文件操作结构体
static const struct file_operations fops =
{
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
//.read = hello_read,
//.write = hello_write,
.unlocked_ioctl = hello_ioctl,
};
static void setup_cdev(int index)
{
int err, devno = MKDEV(major, index);
cdev_init(hello_cdev[index], &fops);
hello_cdev[index]->owner = THIS_MODULE;
hello_cdev[index]->ops = &fops;
err = cdev_add(hello_cdev[index], devno, 1);
if(err)
{
printk(KERN_NOTICE "Error %d adding hello%d", err, index);
}
}
void call_back(unsigned long val)
{
printk("timer is out\n");
//事件通知上层运用
if(eve_ctx)
{
eventfd_signal(eve_ctx, 1);
}
tm.expires = jiffies + 2 * HZ;
add_timer(&tm);
}
static void __init hello_init(void)
{
//申请设备号,动态or静态
int ret = 0;
if(major)
{
//为字符设备静态申请第一个设备号
dev = MKDEV(major, minor);
ret = register_chrdev_region(dev, HELLO_CNT, "hello");
}
else
{
//为字符设备动态申请一个设备号
ret = alloc_chrdev_region(&dev, minor, HELLO_CNT, "hello");
major = MAJOR(dev);
}
//构造cdev设备对象
int i = 0;
for(i = 0; i < HELLO_CNT; ++i)
{
hello_cdev[i] = cdev_alloc();
}
//初始化设备对象
for(minor = 0; minor < HELLO_CNT; ++minor)
{
setup_cdev(minor);
}
hello_class = class_create(THIS_MODULE, "hello");
for(minor = 0; minor < HELLO_CNT; ++minor)
{
hello_class_dev[minor] = device_create(hello_class, NULL, MKDEV(major, minor), NULL, "hello%d",minor);
}
//初始化定时器
init_timer(&tm);
tm.function = call_back;
tm.expires = jiffies + 2 * HZ;
add_timer(&tm);
}
static void __exit hello_exit(void)
{
for(minor = 0; minor < HELLO_CNT; ++minor)
{
device_destroy(hello_class, MKDEV(major, minor));
}
class_destroy(hello_class);
//从内核注销cdev设备对象
cdev_del(hello_cdev);
//回收设备号
unregister_chrdev_region(dev, HELLO_CNT);
//回收eventfd context
if(eve_ctx)
{
eventfd_ctx_put(eve_ctx);
eve_ctx = NULL;
}
//注销定时器
del_timer(&tm);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
5. 用户空间
test_ioctl.c
/*
* =====================================================================================
*
* Filename: ioctl.c
*
* Description:
*
* Version: 1.0
* Created: 08/27/17 14:18:42
* Revision: none
* Compiler: gcc
*
* Author: linsheng.pan (), life_is_legend@163.com
* Organization:
*
* =====================================================================================
*/
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include "command.h"
int main(int argc, char *argv[])
{
int fd = open("/dev/hello0", O_RDWR);
if(fd < 0)
{
perror("open error:");
return -1;
}
if(ioctl(fd, HELLO_RESET) < 0)
{
perror("error:");
return -1;
}
int val = 1;
if(ioctl(fd, HELLO_WRITE, &val) < 0)
{
perror("write error:");
return -1;
}
val = 2;
if(ioctl(fd, HELLO_READ, &val) < 0)
{
perror("read error");
return -1;
}
printf("val = %d\n", val);
return 0;
}