typedef struct {
int counter;
} atomic_t;
ATOMIC_INIT(int i) 定义原子变量的时候对其初始化。
int atomic_read(atomic_t *v) 读取 v 的值,并且返回。
void atomic_set(atomic_t *v, int i) 向 v 写入 i 值。
void atomic_add(int i, atomic_t *v) 给 v 加上 i 值。
void atomic_sub(int i, atomic_t *v) 从 v 减去 i 值。
void atomic_inc(atomic_t *v) 给 v 加 1,也就是自增。
void atomic_dec(atomic_t *v) 从 v 减 1,也就是自减
int atomic_dec_return(atomic_t *v) 从 v 减 1,并且返回 v 的值。
int atomic_inc_return(atomic_t *v) 给 v 加 1,并且返回 v 的值。
int atomic_sub_and_test(int i, atomic_t *v) 从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v) 从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v) 给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v) 给 v 加 i,如果结果为负就返回真,否则返回假
void set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1。
void clear_bit(int nr,void *p) 将 p 地址的第 nr 位清零。
void change_bit(int nr, void *p) 将 p 地址的第 nr 位进行翻转。
int test_bit(int nr, void *p) 获取 p 地址的第 nr 位的值。
int test_and_set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
int test_and_clear_bit(int nr, void *p) 将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
int test_and_change_bit(int nr, void *p) 将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。
atomic_t v = ATOMIC_INIT(0); /* 定义并初始化原子变零 v=0 */
atomic_set(&v, 10); /* 设置 v=10 */
atomic_read(&v); /* 读取 v 的值,肯定是 10 */
atomic_inc(&v); /* v 的值加 1, v=11 */
//结构体 spinlock_t 表示自旋锁,结构体 spinlock_t 实际表示自旋锁的画法
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
DEFINE_SPINLOCK(spinlock_t lock) 定义并初始化一个自选变量。
int spin_lock_init(spinlock_t *lock) 初始化自旋锁。
void spin_lock(spinlock_t *lock) 获取指定的自旋锁,也叫做加锁。
void spin_unlock(spinlock_t *lock) 释放指定的自旋锁。
int spin_trylock(spinlock_t *lock) 尝试获取指定的自旋锁,如果没有获取到就返回 0
int spin_is_locked(spinlock_t *lock) 检查指定的自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0
被自旋锁保护的临界区不能调用任何能够引起睡眠和阻塞的API 函数
被自旋锁保护的临界区<=>后勤办公室里钥匙借还表中管理的所有教室
临界区<=>教室
void spin_lock_irq(spinlock_t *lock) 禁止本地中断,并获取自旋锁。
void spin_unlock_irq(spinlock_t *lock) 激活本地中断,并释放自旋锁。
//flags中断状态:
//挂起态:中断已发生,但是中断没有被处理执行
//激活态:中断发生,正在执行对应的中断处理函数,但是还没有执行结束
//未激活态:中断没有发生。
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags) 保存中断状态,禁止本地中断,并获取自旋锁。
void spin_unlock_irqrestore(spinlock_t*lock, unsigned long flags) 将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁
DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */
/* 线程 A */
void functionA (){
unsigned long flags; /* 中断状态 */
spin_lock_irqsave(&lock, flags) /* 获取锁 */
/* 临界区 */
spin_unlock_irqrestore(&lock, flags) /* 释放锁 */
}
/* 中断服务函数 */
void irq() {
spin_lock(&lock) /* 获取锁 */
/* 临界区 */
spin_unlock(&lock) /* 释放锁 */
}
//mutex 结构体表示互斥体,定义如下(省略条件编译部分):
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
};
DEFINE_MUTEX(name) 定义并初始化一个 mutex 变量。
void mutex_init(mutex *lock) 初始化 mutex。
void mutex_lock(struct mutex *lock) 获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠。
void mutex_unlock(struct mutex *lock) 释放 mutex,也就给 mutex 解锁。
int mutex_trylock(struct mutex *lock) 尝试获取 mutex,如果成功就返回 1,如果失败就返回 0。
int mutex_is_locked(struct mutex *lock) 判断 mutex 是否被获取,如果是的话就返回1,否则返回 0。
int mutex_lock_interruptible(struct mutex *lock)使用此函数获取信号量失败进入休眠以后可以被信号打断。
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
};
//定义好定时器以后还需要通过一系列的 API 函数来初始化此定时器
struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数 */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};
void init_timer(struct timer_list *timer) //负责初始化 timer_list 类型变量,当我们定义了一个 timer_list 变量以后一定要先用 init_timer 初始化一下
void add_timer(struct timer_list *timer) //Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行
int del_timer(struct timer_list * timer) //删除一个定时器
int del_timer_sync(struct timer_list *timer) del_timer //函数的同步版,会等待其他处理器使用完定时器再删除
int mod_timer(struct timer_list *timer, unsigned long expires) //修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!
//unsigned long expires是修改后的超时时间
int jiffies_to_msecs(const unsigned long j)将jiffies 类型的参数 j 分别转换为对应的毫秒
int jiffies_to_usecs(const unsigned long j) 将jiffies 类型的参数 j 分别转换为对应的微秒
u64 jiffies_to_nsecs(const unsigned long j) 将jiffies 类型的参数 j 分别转换为对应的纳秒
long msecs_to_jiffies(const unsigned int m) 将毫秒转换为 jiffies 类型
long usecs_to_jiffies(const unsigned int u) 将微秒转换为 jiffies 类型
unsigned long nsecs_to_jiffies(u64 n) 将纳秒转换为 jiffies 类型
unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 */
/*************************************
具体的代码
************************************/
/* 判断有没有超时 */
if(time_before(jiffies, timeout)) {
/* 超时未发生 */
} else {
/* 超时发生 */
}
struct timer_list timer; /* 定义定时器 */
/* 定时器回调函数 */
void function(unsigned long arg)
{
/*
* 定时器处理代码
*/
/* 如果需要定时器周期性运行的话就使用 mod_timer
* 函数重新设置超时值并且启动定时器。
*/
mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
}
/* 初始化函数 */
void init(void)
{
init_timer(&timer); /* 初始化定时器 */
timer.function = function; /* 设置定时处理函数 */
timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
add_timer(&timer); /* 启动定时器 */
}
/* 退出函数 */
void exit(void)
{
del_timer(&timer); /* 删除定时器 */
/* 或者使用 */
del_timer_sync(&timer);
}
1
上半部就是从中断响应事件中提取出能快速处理的代码,用中断处理函数实现
下半部就是从中断响应事件中提取出不能快速处理的代码,用软中断,tasklet或工作队列实现
2中断控制器应该肯定能暂停pc自动叠加,即cpu停止运行
硬件中断使用中断控制器,
软件中断不使用中断控制器
软中断发生的时间是由程序控制的,而硬中断发生的时间是随机的
3
一些结构体,即一些画像,只是轮廓画像。详细描绘去vscode右键查看每一笔轮廓定义
//示例代码 51.1.2.1 软中断结构体
struct softirq_action
{
void (*action)(struct softirq_action *);//声明一个函数指针action
};
static struct softirq_action softirq_vec[NR_SOFTIRQS];//软中断结构体数组softirq_vec
//示例代码 51.1.2.5 tasklet_struct 结构体
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
//示例代码 51.1.2.8 work_struct 结构体
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
1
申请中断:int request_irq(要申请中断的中断号,中断处理函数,中断标志,中断名字,传递给中断处理函数的第二个参数)
释放掉某个中断:void free_irq(要释放的中断,来区分具体的共享中断)
2
中断使能函数:void enable_irq(中断号)
中断禁止函数:void disable_irq(中断号)
3
不会等待当前中断处理程序执行完毕的中断禁止函数:void disable_irq_nosync(unsigned int irq)
4
使能当前处理器中断系统:local_irq_enable()不需要参数
禁止当前处理器中断系统: local_irq_disable()不需要参数
5
禁止中断,并且将中断状态保存在 flags 中的函数:local_irq_save(flags)
恢复中断,将中断到 flags 状态:local_irq_restore(flags)
6
一般中断服务函数返回值使用形式为:return IRQ_RETVAL(IRQ_HANDLED)
#define IRQ_RETVAL(x)((x) ? IRQ_HANDLED : IRQ_NONE)
如果x为真,则返回IRQ_HANDLED的值;如果x为假,则返回IRQ_NONE的值。
7
获取中断号:unsigned int irq_of_parse_and_map(设备节点,索引号)
索引号: (interrupts )中断属性可能包含多条中断信息,通过 index 指定要获取的信息
注册对应的软中断处理函数:void open_softirq(i要开启的软中断, 软中断对应的处理函数指针)
软中断触发函数:void raise_softirq(要触发的软中断)
初始化 tasklet:void tasklet_init(要初始化的tasklet,tasklet的处理函数指针,要传递给tasklet的处理函数的参数)
一 次 性 完成 tasklet 的 定 义 和 初 始 化:DECLARE_TASKLET( tasklet 名字, tasklet 的处理函数指针,要传递给tasklet的处理函数的参数)
使 tasklet 在合适的时间运行需要调用的函数:void tasklet_schedule(要调度的tasklet)
初始化工作函数:INIT_WORK(工作的结构体类型指针,工作的处理函数)
一次性完成工作的创建和初始化的函数:DECLARE_WORK(工作的结构体类型指针,工作的处理函数)
使 工作在合适的时间运行需要调用的函数:bool schedule_work(要调度的工作)
//示例代码 51.1.2.7 tasklet 使用示例
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 tasklet */
tasklet_schedule(&testtasklet);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
//示例代码 51.1.2.11 工作队列使用示例
/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 work */
schedule_work(&testwork);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 work */
INIT_WORK(&testwork, testwork_func_t);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
其实就是两个不同角度的 I/O 划分方式。它们描述的对象也不同,阻塞 / 非阻塞针对的是 I/O 调用者(即应用程序),而同步 / 异步针对的是 I/O 执行者(即系统)。
根据应用程序是否阻塞自身运行(schedule给其他"人"运行),可以把 I/O 分为阻塞 I/O 和非阻塞 I/O。
所谓阻塞 I/O,是指应用程序在执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,不能执行其他任务。
所谓非阻塞 I/O,是指应用程序在执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务。
//1、初始化和定义
void init_waitqueue_head(要初始化的等待队列头) //初始化等待队列头
DECLARE_WAIT_QUEUE_HEAD //一次性完成等待队列头的定义的初始化。
DECLARE_WAITQUEUE(等待队列项的名字, 这个等待队列项属于的任务(进程)) // 定义并初始化一个等待队列项
//2、将队列项添加/移除等待队列头
void add_wait_queue(等待队列项要加入的等待队列头,要加入的等待队列项) //添加等待队列项
void remove_wait_queue(要删除的等待队列项所处的等待队列头,要删除的等待队列项) //移除等待队列项
//3、等待唤醒
void wake_up(要唤醒的等待队列头) //可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程
void wake_up_interruptible(要唤醒的等待队列头) //只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程
//4、等待事件
wait_event(要等待被唤醒的等待队列头, 条件) //等待某队列头被唤醒,并且条件满足后解阻塞
wait_event_timeout(要等待被唤醒的等待队列头, 条件,最多等待的时长) //等待某队列头被唤醒,并且条件满足后解阻塞;或者超时后解阻塞。返回 0 的话表示超时时间到,而且 condition为假。为 1 的话表示 condition 为真,也就是条件满足了
wait_event_interruptible(要等待被唤醒的等待队列头, 条件) //等待某队列头被唤醒,并且条件满足后解阻塞,可以被信号打断
wait_event_interruptible_timeout(要等待被唤醒的等待队列头, 条件,最多等待的时长)//等待某队列头被唤醒,并且条件满足后解阻塞;超时后解阻塞;可以被信号打断。返回 0 的话表示超时时间到,而且 condition为假。为 1 的话表示 condition 为真,也就是条件满足了
如何用select,在APP中直接调用select()
int select(所要监视的这三类文件描述集合中, 最大文件描述符加 1,要监视的是否可以读取的目标文件集合,要监视的是否可以进行写操作的目标文件集合,要监视的是否有异常的目标文件集合,最多等待的时长)//轮询监视返回有n个可操作文件,返回-1发生错误,返回0最多等待的时长到达了
//对文件描述符的宏操作
void FD_ZERO(fd_set *set) //将文件集合set变量的所有位都清零
void FD_SET(int fd, fd_set *set) //向文件集合set中添加一个文件描述符
void FD_CLR(int fd, fd_set *set) //将一个文件描述符从文件集合set中删除
int FD_ISSET(int fd, fd_set *set) //测试文件fd是否属于文件集合set
//示例代码 52.1.3.1 select 函数非阻塞读访问示例
void main(void)
{
int ret, fd; /* 要监视的文件描述符 */
fd_set readfds; /* 读操作文件描述符集 */
struct timeval timeout; /* 超时结构体 */
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
FD_ZERO(&readfds); /* 清除 readfds */
FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */
/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 超时 */
printf("timeout!\r\n");
break;
case -1: /* 错误 */
printf("error!\r\n");
break;
default: /* 可以读取数据 */
if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 */
/* 使用 read 函数读取数据 */
}
break;
}
}
如何用epoll ,在APP中直接调用以下API
int epoll_create(从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以)//创建一个 epoll 句柄
int epoll_ctl(要操作的 epoll 句柄,要对epoll 句柄进行的操作,要监视的文件描述符,要监视的事件类型)//向epoll中添加要监视的文件描述符以及监视的事件
int epoll_wait(要等待的 epoll,指向 epoll_event 结构体的数组的指针,events 数组大小,最多等待的时长)//等待事件的发生
如何用 poll
int poll(要监视的文件描述符集合以及要监视的事件所组成的数组,要监视的文件,最多等待的时长)//轮询监视返回有n个可操作文件,返回-1发生错误,返回0最多等待的时长到达了
//示例代码 52.1.3.2 poll 函数读非阻塞访问示例
void main(void)
{
int ret;
int fd; /* 要监视的文件描述符 */
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
/* 构造结构体 */
fds.fd = fd;
fds.events = POLLIN; /* 监视数据是否可以读取 */
ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时 500ms */
if (ret) { /* 数据有效 */
......
/* 读取数据 */
......
} else if (ret == 0) { /* 超时 */
......
} else if (ret < 0) { /* 错误 */
......
}
}
驱动程序的编写者如何提供对应的 poll 函数(poll轮询,非阻塞)
//我们需要在驱动程序的poll函数中调用 poll_wait 函数, poll_wait 函数不会引起阻塞,只是将应用程序添加到 poll_table 中
//unsigned int poll(设备文件,若干等待队列组成的等待列表指针) 对驱动程序进行非阻塞访问
//void poll_wait(设备文件,等待队列头, poll_table的地址)将应用程序添加到 poll_table 中
/*
* @description : poll 函数,用于处理非阻塞访问
* @param - filp : 要打开的设备文件(文件描述符)
* @param - wait : 等待列表(poll_table)
* @return : 设备或者资源状态,
*/
unsigned int imx6uirq_poll(struct file *filp,
struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)
filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
if(atomic_read(&dev->releasekey)) { /* 按键按下 */
mask = POLLIN | POLLRDNORM; /* 返回 PLLIN */
}
return mask;
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.poll = imx6uirq_poll,
};
根据 I/O 响应的通知方式的不同,可以把文件 I/O 分为同步 I/O 和异步 I/O。
所谓同步 I/O,是指收到 I/O 请求后,系统不会立刻响应应用程序;等到处理完成,系统才会通过系统调用的方式,告诉应用程序 I/O结果。
所谓异步 I/O,是指收到 I/O 请求后,系统会先告诉应用程序 I/O;请求已经收到,随后再去异步处理;等处理完成后,系统再通过事件通知的方式,告诉应用程序结果。
驱动程序编写
/*
* @description : fasync 函数,用于处理异步通知
* @param - fd : 文件描述符
* @param - filp : 要打开的设备文件(文件描述符)
* @param - on : 模式
* @return : 负数表示函数执行失败
*/
static int imx6uirq_fasync(int fd, struct file *filp, int on)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)
filp->private_data;
return fasync_helper(fd, filp, on, &dev->async_queue);
}
/*
* @description : release 函数,应用程序调用 close 关闭驱动文件的时候会执行
* @param – inode : inode
* @param – filp : 要打开的设备文件(文件描述符)
* @return : 负数表示函数执行失败
*/
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
return imx6uirq_fasync(-1, filp, 0);
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.poll = imx6uirq_poll,
.fasync = imx6uirq_fasync,
.release = imx6uirq_release,
};
sighandler_t signal(要设置处理函数的信号, 信号的处理函数指针) //设置指定信号的处理函数信号处理函数原型如下所示:typedef void (*sighandler_t)(int)
应用程序对异步通知的处理
应用程序对异步通知的处理包括以下三步:
1、注册信号处理函数
应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来
设置信号的处理函数。前面已经详细的讲过了,这里就不细讲了。
sighandler_t signal(要设置处理函数的信号, 信号的处理函数指针) //设置指定信号的处理函数信号处理函数原型如下所示:typedef void (*sighandler_t)(int)
2、将本应用程序的进程号告诉给内核
使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。 告诉内核,发给谁
3、开启异步通知
fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性
使用如下两行程序开启异步通知:
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能。改变fasync标记,最终会调用到驱动的faync > fasync_helper:初始化/释放fasync_struct */
重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函
数就会执行。
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
#include "signal.h"
static int fd = 0; /* 文件描述符 */
/*
* SIGIO信号处理函数
* @param - signum : 信号值
* @return : 无
*/
static void sigio_signal_func(int signum)
{
int err = 0;
unsigned int keyvalue = 0;
err = read(fd, &keyvalue, sizeof(keyvalue));
if(err < 0) {
/* 读取错误 */
} else {
printf("sigio signal! key value=%d\r\n", keyvalue);
}
}
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int flags = 0;
char *filename;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
/* 设置信号SIGIO的处理函数 */
signal(SIGIO, sigio_signal_func);
fcntl(fd, F_SETOWN, getpid()); /* 设置当前进程接收SIGIO信号 */
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 设置进程启用异步通知功能 */
while(1) {
sleep(2);
}
close(fd);
return 0;
}