/****************************异步通知************************************/
/*
*用户空间应用程序:
*为了启动文件的异步通知机制,用户应用程序必须执行以下两个步骤:
*(1)他们指定一个进程作为文件的属主,当进程使用fcntl系统调用执行F_SETOWN命令时,属主进程的进程ID号就被保存在
* filp->f_owner中,这一步是必须的,目的是为了 让内核知道应该通知那个进程.
*(2)为了真正启动异步通知,用户应用程序还必须在设备中设置FASYNC标志,这通过fcntl的F_SETFL命令完成的,执行完
* 这两步输入文件就可以在新数据到达时请求发送一个SIGIO信号 该信号被发送到存在filp->f_owner中的进程.
*/
//综上所知:用户的应用程序必须执行下面的语句
int oflags;
signal(SIGIO, &input_handler); //input_handler为信号处理函数
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
//不是所有的设备都支持异步通知,应用程序通常假设只有套接字和终端才有异步通知能力
/*
驱动程序:
(1) 当 F_SETOWN被调用时, 除了一个值被赋值给filp->f_owner.什么都也不做
(2) 在执行 F_SETFL启动 FASYNC时, 驱动的 fasync 方法被调用. 只要 filp->f_flags 中的FASYNC 标志发生了
变化,就会调用该方法,以便把这个变化通知驱动程序 使其能正确响应,文件打开时,FASYNC标志被默认为是清除的
(3) 当数据到达, 所有的注册异步通知的进程必须被发出一个 SIGIO 信号
*/
//程序设计如下:
struct xxx_dev{
........;
........;
........;
struct fasync_struct *async_quene;
};
static int xxx_fasync (int fd, struct file *filp, int mode)
//当打开的文件的FASYNC标志被修改时,调用fasync_helper以便从相关进程列表中增加或删除文件
{
struct xxx_dev *dev=(struct xxx_dev *)filp->private_data; //获取执行设备描述结构的指针,在open函数中设置
return fasync_helper(fd,filp,mode,&dev->async_quene) ; //处理FASYNC更新
}
static struct file_operations xxx_fops={
.............
.............
.fasync=xxx_fasync,
};
if(dev->async_quene)
kill_fasync(&xxx_dev->async_quene,SIGIO,POLL_IN(或POLL_OUT(对设备可写)));
//当数据到达时,执行此语句来异步通知进程可读或可写,即释放信号
xxx_fasync(-1,filp,0); //将文件从异步通知列表中删除 ,一般在release中实现
/**************************************end****************************************************/
/*********************************内核定时器相关操作**********************************************/
/*====================延时=====================*/
/*
*HZ为1s
*jiffies为内核计数器(记录当前时间),驱动通常访问这个变量unsigned long time_val=jiffies;
*/
//下面的函数用于比较时间:
#include <linux/jiffies.h>
int time_after(unsigned long a, unsigned long b);
int time_before(unsigned long a, unsigned long b);
int time_after_eq(unsigned long a, unsigned long b);
int time_before_eq(unsigned long a, unsigned long b);
//timespec和timeval与jiffies时间的相互转换函数:
#include <linux/time.h>
unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
//延时:
#include <linux/sched.h>
set_current_state(TASK_interruptible);
//设置当前进程状态为阻塞(以便将进程在给定的时间内休眠)
schedule_timeout(delay);
//将当前任务睡眠指定的时间后重新调度执行,正常返回值为0,除非在给定的超时时间到期之前返回
schedule_timeout_uninterruptible(delay);
schedule_timeout_interruptible(delay);
//短延时:
#include <linux/delay.h>
void ndelay(unsigned long nsecs); //纳秒
void udelay(unsigned long usecs); //微妙
void mdelay(unsigned long msecs); //毫秒
//udelay和ndelay位传递给他们的值强加了上限,这点要注意 ,另外这三个延迟函数均是忙等待函数,因而在延迟过程中无法运行其他任务,
//我们应该在没有其他实用方法时使用这些函数
void msleep(unsigned int millisecs); //将调用它的进程休眠以给定的毫秒
unsigned long msleep_interruptible(unsigned int millisecs);
//将调用它的进程休眠以给定的毫秒,其正常返回值是0,如果被提前唤醒,那么返回值就是剩余的毫秒数
void ssleep(unsigned int seconds); //将调用它的进程休眠以给定的秒
/*===============================================*/
/*==================内核定时器===================*/
/*
* 如果我们需要在将来的某个时间点调度执行某个函数,同时在该时间点到达之前不会阻塞当前进程(相当于中断),则可以使用内核定时器。
* 内核定时器可用来在未来的某个 特定时间点调度执行某个函数,从而可用于完成许多任务,一个内核定时器是一个数据结构,它告诉内核在
* 用户定义的时间点使用用户定义的参数来执行一个用户定义的函数,内核定时器常常是作为“软件中断”的结果而运行的,定时器函数必须以
* 自旋锁和原子上下文的方式原子地运行,内核定时器一个重要的特点是任务可以将自己注册以在稍后的时间重新启动一个注册自己的定时器
* 始终会在同一CPU上运行,即使在单处理器系统上,定时器也会是竞态的潜在的来源,这是由其异步执行的特点直接导致的,因此,任何通
* 过定时器函数访问的数据结构都应该针对并发访问进行保护,可以使用原子变量或自旋锁
*/
//内核为驱动程序提供了一组用来声明、注册和删除内核定时器的函数,如下所示:
#include <linux/timer.h>
struct timer_list { //该结构在使用前必须调用init_timer 或TIMER_INITIALIZER来初始化
struct list_head entry; //定时器列表
unsigned long expires; //表示期望定时器执行的jiffies值,到达该jiffies值时将调用function函数,并传递data作为参数(expires=jiffies+X*HZ,)
void (*function)(unsigned long);
unsigned long data;
//function函数指针指向的的处理函数的参数,如果需要通过这个参数传递多个数据项,那么可以将这些数据项捆绑成一个数据结构,
然后将该数据结构的指针强制转换成unsigned long 传入 expires、function、data是可由定时器代码以外的代码访问的
struct tvec_base *base; //如果base为NULL,定时器尚未调度运行,否则该指针会告诉我们那个数据结构 ( 哪个cpu ) 在运行定时器
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
//上述该结构在使用前必须初始化,初始化可确保所有的字段被正确设置,包括那些对调用者不可见的字段,通过调用init_timer 或TIMER_INITIALIZER
//赋以某个静态的结构,即可完成初始化,初始化之后,可在调用add_timer函数注册之前修改expires、function、data这三个公共字段,如果要在定时
//器到期前禁止一个已注册的定时器,则可以调用del_timer函数
void init_timer(struct timer_list *timer); //初始化定时器
或
struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
void add_timer(struct timer_list *timer); //注册定时器(启动定时器)
int del_timer(struct timer_list *timer); //注销定时器
int mod_timer(struct timer_list *timer,unsigned long expires);
//更新某个定时的到期时间,经常用于超时定时器,我们也可以在通常使用add_timer的时候在不活动的定时器上调用
//mod_timer(expires设定的时间值后再次启动定时器)
int del_timer_sync(struct timer_list *timer);
//和del_timer工作类似,但该函数可确保在返回时没有任何CPU在运行定时器函数,大多数情况下考虑使用它而不是del_timer
//在拥有锁时,应格外小心调用del_timer_sync,因为如果定时器函数企图获取相同的锁,系统就会进入死锁
int timer_pending(const struct timer_list *timer);
//该函数通过读取timer_list结构不可见字段来返回定时器是否正在被调度运行
/*===============================================*/
/*********************************end****************************************/
/*******************************tasklet(小任务)********************************/
/*
*tasklet(小任务):
* 中断管理中大量使用了这种机制,tasklet在很多方面类似内核定时器:它们始终在中断期间运行,始终会在调度它们的同一CPU上运行,而且都接收一个unsigned long类型的参数,但我们* 不能要求 tasklet在某个给定的时间执行,调度一个tasklet,表明我们希望内核选择某个其后的时间来执行给定的函数
* 一个tasklet可在稍后被禁止或者重新启动,只有启动的次数和禁止的次数相同时,tasklet才会被执行。tasklet可以注册自己本身。tasklet可被调度以通常的优先级或高优先级执行,如 果 系统负荷不重,则tasklet会立即得到执行tasklet以结构体形式的数据结构的形式存在,并在使用前必须初始化,可调用下面函数中的一个进行初始化
*/
#include <linux/interrupt.h>
struct tasklet_struct //小任务
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data; //是func函数指针指向的函数的参数
};
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data); //初始化
DECLARE_TASKLET(name,func,data); //初始化
DECLARE_TASKLET_DISABLED(name,func,data); //初始化
void tasklet_disable(struct tasklet_struct *t);
//禁止指定的tasklet,该tasklet仍然可以用tasklet_schedule调度,但其执行被推迟,直到该tasklet被重新启动。如果终止当前tasklet正在运行,该函数会
//进入忙等待直到tasklet退出为止,因此,在调用tasklet_disable之后,我们可以确信该tasklet不会在系统中的任何地方运行
void tasklet_disable_nosync(struct tasklet_struct *t);
//禁止指定的tasklet,但不会等待任何正在运行的tasklet退出,该函数返回后,tasklet是禁止的,而且在重新启动之前,不会再次被调度,
//但是 当该函数返回时,指定的tasklet可能仍在其他cpu上运行
void tasklet_enable(struct tasklet_struct *t);
//启动一个先前被禁止的tasklet,如果该tasklet已经被调度,它很快就会运行;对tasklet_enable调用和每个对tasklet_disable的调用匹配
void tasklet_schedule(struct tasklet_struct *t); //调度执行指定的tasklet
void tasklet_hi_schedule(struct tasklet_struct *t);
//调度执行指定的tasklet以高优先级执行,理想状态下只有具备低延迟需求的任务才能使用这个函数
void tasklet_kill(struct tasklet_struct *t);
//该函数确保指定的tasklet不会被再次调度运行,当设备要被关闭或模块要移除时,我们通常调用这个函数,如果tasklet正被调度执行,该函数会等待其退出
/****************************************************end**********************************************************************/
/**************************************************工作队列*******************************************************************/
//工作队列类似tasklet,它们都允许内核代码请求某个函数在将来的时间被调用,tasklet代码必须是原子的,而工作队列函数是可以休眠的
#include <linux/workqueue.h>
struct workqueue_struct { //工作队列
struct cpu_workqueue_struct *cpu_wq;
struct list_head list;
const char *name;
int singlethread;
int freezeable; /* Freeze threads during suspend */
int rt;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
//在使用之前,必须显示地创建一个工作队列,使用下面函数:
//每个工作队列有一个或多个专用的进程,这些进程运行提交到该队列的函数
struct workqueue_struct *creat_workqueue(const char *name);
//使用它,内核会在系统中的每个处理器上为该工作队列创建专用的线程
struct workqueue_struct *creat_singlethread_workqueue(const char *name); //如果单个工作线程足够使用,那么应该使用creat_singlethread_workqueue创建工作队列
//向一个工作队列提交一个任务,需要构造一个work_struct结构,可通过下面接口完成:
DECLARE_WORK(name,void(*function)(void*),void *data); //name是要声明的结构名称,function是要从工作队列中调用的函数,data是要传递给该函数的值
//或动态运行时
INIT_WORK(struct work_struct *work,void(*function)(void*),void *data );
//该函数完成更加彻底的结构初始化,在首次构造该结构时,应该使用它
PREPARE_WORK(struct work_struct *work,void(*function)(void*),void *data );
//PREPARE_WORK完成几乎相同的工作,但它不会初始化用来将work_struct结构链接到工作队列的指针,
//如果结构已经被提交到工作队列,而只是修改该结构,则应该使用它而不是INIT_WORK
//将工作提交到工作队列,使用下面两个函数之一:
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *wq,struct delayed_work *dwork, unsigned long delay);
//它们都会将dwork->work添加到给定的queue,但queue_delayed_work会延时,如果添加成功,则返回1,在将来的某个时间
//工作函数会被调用,并传入给定的data值,该函数不能访问用户空间
//如果要取消某个挂起的工作队列入口项,可调用:
int cancel_delayed_work(struct work_struct *work);
//如果该入口项在开始执行前被取消,则该函数返回非0,在调用它之后,内核会确保不会初始化给定入口项的执行,但是如果cancel_delayed_work返回0,
//则说明 该入口项已经在其他处理器上运行,因此在cancel_delayed_work返回后可能仍在运行,为了绝对确保在cancel_delayed_work返回0后,
//工作函数不会在系统中的任何地方运行,则应该随后调用下面函数
void flush_workqueue(struct workqueue_struct *queue);
//它被调用返回后,任何在调用之前被提交的工作函数都不会在系统任何地方运行
void destroy_workqueue(struct workqueue_struct *queue);
//在结束对工作队列的使用后,可调用该函数释放资源
int schedule_work(struct work_struct *work);//调度工作队列执行
int schedule_delayed_work(struct work_struct *work,unsigned long delay);
//调度工作队列在给定的时间后执行
/***************************************end**************************************************/
/*************************************中断处理*************************************************/
#include <linux/sched.h>
int request_irq(unsigned int irq,irqreturn_t (*handler)(int, void *),unsigned long flags,const char *dev_name,void *dev_id); //申请中断,成功返回0,负值表示错误码
//函数返回-EBUSY表示该信号线已经被占用
//irq为要申请的中断号,在平台相关目录下的irqs.h中定义
//handler为中断处理函数,其原型为:irqreturn_t xxx_irq(int irq,void *dev_id)
//flags为中断触发方式或中断管理相关的位掩码,在interrupt.h中定义,
//dev_name为中断名,可以根据需要随便写
//dev_id用于共享中断信号线,驱动程序一般使用它指向驱动程序的私有数据区(用来识别那个设备产生中断)
//中断的申请最好在打开设备时
int free_irq(unsigned int irq,void *dev_id);
//中断处理程序:
irqreturn_t xxx_irq(int irq,void *dev_id) //irqreturn_t =int
{
struct xxx_dev *dev=dev_id;
........
wake_up_interruptible(xxx);
return IRQ_RETVAL(IRQ_HANDLED);//中断处理程序应该返回一个值,用来表明是否真正处理了一个中断,如果中断例程发现其设备的确要处理,则应该返回IRQ_HANDLED,
//否则应该返回IRQ_NONE,我们可以通过这个宏来产生返回值,不是本设备的中断应该返回IRQ_NONE
}
/*
*中断处理程序中不能向用户空间发送或者接收数据,也不能做任何可能发送休眠的操作并且不能调用schdule函数
*中断处理程序一个典型的任务就是:如果中断通知进程所等待的事情已经发生,比如新的数据到达,就会唤醒在该设备上休眠的进程
*无论是快速还是慢速处理例程,中断服务程序执行时间应尽可能的短,如果需要执行一个长时间的计算任务,最好的方法是使用tasklet或者工作队列在更安全的时间内调度计算任务
*可通过命令查看系统使用的中断号:cat /proc/interrupts
*/
//禁止当前中断:
#include <asm/irq.h>
void disable_irq(int irq); //禁止某个特定的中断线,而且还会等待当前正在执行的中断处理例程完成
void disable_irq_nosync(int irq);//功能同disable_irq,但会立即返回
void enable_irq(int irq); //使能中断
//禁止所有中断:
#include <asm/system.h>
void local_irq_save(unsigned long flags);//把当前中断状态保存到flags中,然后禁止当前处理器上的中断
void local_irq_disable(void);//直接禁止中断,只有在我们知道中断并未在其他地方被禁止的情况下才能使用它
//打开所有的中断:
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);
/*
linux通过中断处理例程分成两部分来解决中断要完成一定数据的工作问题,“顶半部”的部分用于完成必要的工作,用于快速响应,也就是 request_irq注册的中断例程,而所谓的“底半部”是一个
被顶半部调度,并在稍后更安全的时间内执行的例程,与顶半部最大的不同是底半部处理例程执行时,所有的中断都是打开的,典型的情况是顶半部保存设备的数据到一个特定的缓冲区并调度它的底半部,然后退出。
linux提供了两种机制用来实现底半部处理,tasklet通常是底半部的优选机制,因为这种机制非常快,但所有的tasklet代码必须是原子的。还可以选择工作队列,它可以具有更高的延迟,但允许休眠。
tasklet可确保和第一次调度它们的函数运行在同样的cpu上,这样,因为tasklet在中断处理例程结束前不会开始运行,所以此时的中断处理例程是安全的,在tasklet运行时,可以有其他中断,因此在tasklet
和中断例程之间的锁还是需要的
*/
//共享中断:
1:flags被设置为IRQF_SHARED
2:dev_id参数必须是唯一的,任何指向模块的地址空间的指针都可以用,但它不能被设置为NULL。
/********************************end***********************************************/
还是以昨天的例子,今天只是在昨天的例子上改写了一下,加上了一个应用程序
【主程序】修改部分:
// 1. 异步通知队列
#include <linux/fs.h>
... ...
// 2. 定义消息队列
struct fasync_struct *fasync_queue;
... ...
ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
char fifo_buf[50];
ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));
if (ret < 0) {
ret = 0;
goto exit;
}
ret = copy_to_user(buf, fifo_buf, 50);
if (ret) {
ret = -ENOMEM;
} else {
ret = 50;
}
exit:
return ret;
}
ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
char fifo_buf[50];
if (size > 50) {
size = 50;
}
ret = copy_from_user(fifo_buf, buf, size);
if (ret) {
ret = -ENOMEM;
} else {
ret = size;
printk("%s\n", fifo_buf);
}
fifo_write(&fifo, fifo_buf);
printk("write\n");
// 4. 发送消息到异步消息队列
kill_fasync(&fasync_queue, SIGIO, POLL_IN);
return ret;
}
// 3. 异步消息队列绑定到设备(文件)
// fcntl(fd, F_SETFL)
/*
* @brief 异步消息队列绑定到文件
* @param[in] fd 应用程序传下来
* @param[in] filp 对应fd
* @param[in] mode 设置到绑定函数中
* @return 绑定函数返回
*/
int char_fasync(int fd, struct file *filp, int mode)
{
// 绑定消息队列到文件
return fasync_helper(fd, filp, mode, &fasync_queue);
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = char_open,
.read = char_read,
.write = char_write,
.unlocked_ioctl = char_ioctl,
// 3. 异步消息队列绑定到设备(文件)
.fasync = char_fasync,
.release = char_release,
};
【应用程序】
/*
* 1. 接收信号实现 1-3
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
// 1. 信号头文件
#include <signal.h>
int fd = 0;
// 2. 创建信号处理函数
/*
* @brief 信号处理函数
* @param[in] signo 信号
*/
void sigio_handler(int signo)
{
int ret = 0;
char buf[200];
printf("sigio_handler\n");
ret = read(fd, buf, sizeof(buf));
if (ret > 0) {
printf("%s\n", buf);
}
}
int main(void)
{
int oflags;
// 打开设备
fd = open("/dev/char_demo", O_RDWR, S_IRUSR | S_IWUSR);
if (-1 == fd) {
fprintf(stderr, "open: %s\n", strerror(errno));
exit(0);
}
// 3.消息和处理函数的绑定
signal(SIGIO, sigio_handler);
// 4. 绑定进程到设备
fcntl(fd, F_SETOWN, getpid());
// 5. 通知驱动,绑定异步消息队列到设备
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
while (1) sleep(100);
return 0;
}
2. tasklet实现
/*
* tasklet使用注意事项:
* 1. 处理时间要短
* 2. 不能调用可能引起休眠的函数
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
// 1. 头文件 tasklet
#include <linux/interrupt.h>
struct cdev *char_demo;
struct class * char_demo_class;
struct device *char_demo_device;
// 2. 定义tasklet变量
struct tasklet_struct tasklet;
int major = 0;
int minor = 0;
int char_open(struct inode *inode, struct file *filp)
{
printk("char open\n");
return 0;
}
ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
// 4. tasklet调度
tasklet_schedule(&tasklet);
return ret;
}
ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
return ret;
}
int char_release(struct inode *inode, struct file *filp)
{
printk("char release\n");
return 0;
}
void tasklet_func_demo(unsigned long data)
{
printk("tasklet_func_demo: %ld\n", data);
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = char_open,
.read = char_read,
.write = char_write,
.release = char_release,
};
int __init char_init(void)
{
int ret = 0;
dev_t devno;
if (major != 0) {
devno = MKDEV(major, 0);
ret = register_chrdev_region(devno, 1, "char demo");
if (ret) {
goto register_chrdev_region_err;
}
} else {
ret = alloc_chrdev_region(&devno, minor, 1, "char demo");
if (ret) {
goto register_chrdev_region_err;
}
major = MAJOR(devno);
}
char_demo = cdev_alloc();
if (NULL == char_demo) {
ret = -ENOMEM;
goto cdev_alloc_err;
}
// 3. tasklet变量初始化
/*
* @brief 初始化tasklet
* @param[out] t tasket结构体
* @param[in] func tasklet处理函数
* @param[in] data 传给func(一般传设备的结构体指针)
*/
// void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
tasklet_init(&tasklet, tasklet_func_demo, 2);
char_demo->owner = THIS_MODULE;
char_demo->ops = &fops;
ret = cdev_add(char_demo, devno, 1);
if (ret) {
goto cdev_add_err;
}
char_demo_class = class_create(THIS_MODULE, "char_demo");
if (IS_ERR(char_demo_class)) {
ret = PTR_ERR(char_demo_class);
goto class_create_err;
}
char_demo_device = device_create(char_demo_class, NULL, devno, NULL, "chardev");
if (IS_ERR(char_demo_device)) {
ret = PTR_ERR(char_demo_class);
goto device_create_err;
}
goto register_chrdev_region_err;
device_create_err:
class_destroy(char_demo_class);
class_create_err:
cdev_del(char_demo);
cdev_add_err:
cdev_alloc_err:
unregister_chrdev_region(devno, 1);
register_chrdev_region_err:
return ret;
}
void __exit char_exit(void)
{
dev_t devno = MKDEV(major, minor);
device_destroy(char_demo_class, devno);
class_destroy(char_demo_class);
cdev_del(char_demo);
unregister_chrdev_region(devno, 1);
}
module_init(char_init);
module_exit(char_exit);
MODULE_LICENSE("GPL");
3. 工作队列实现
和上面的tasklet的实现差不多,只需要修改几个函数就行了
... ...
// 1. 工作队列
#include <linux/workqueue.h>
... ...
// 2. 工作队列变量
struct work_struct wq;
... ...
ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
schedule_work(&wq);
return ret;
}
/*
* @brief 工作队列处理函数,实现中断下半部
* @param[in] work 工作队列节点地址
*/
void workqueue_func_demo(struct work_struct *work)
{
printk("workqueue_func_demo\n");
}
ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
schedule_work(&wq);
return ret;
}
int __init char_init(void)
{
... ...
// 3. 初始化工作队列
/*
* wq 工作队列节点
* workqueue_func_demo 工作队列处理函数
*/
INIT_WORK(&wq, workqueue_func_demo);
char_demo->owner = THIS_MODULE;
char_demo->ops = &fops;
ret = cdev_add(char_demo, devno, 1);
if (ret) {
goto cdev_add_err;
}
... ...
}
4. 内核定时器的实现
同理,和上面的工作队列的实现差不多,只需要修改几个地方就行了
... ...
// 1. 定时器头文件
#include <linux/timer.h>
... ...
// 2. 定义定时器变量
struct timer_list timer;
... ...
// 4. 实现timer处理函数
/*
* @brief 定时器处理函数
* @param[in] data 设备的结构体指针
*/
void timer_func_demo(unsigned long data)
{
printk("timer_func_demo: %ld\n", data);
// 5. 修改定时器超时值
/*
* brief 修改定时器超时值
* @param[out] timer 定时器
* @param[in] expires 到期值
* jiffies 系统从启动到当前的执行时间
* HZ 没秒钟产生时钟中断次数
*/
mod_timer(&timer, jiffies + HZ);
}
int __init char_init(void)
{
... ...
// 3. 定时器初始化
/*
* @brief 初始化定时器成员为0
*/
init_timer(&timer);
/*
* 设置定时器超时值
* jiffies 系统从启动到当前发生时钟中断的次数
* HZ 系统每秒钟产生多少次时钟中断
*/
timer.expires = jiffies + HZ;
// 时钟处理函数设置
timer.function = timer_func_demo;
// 时钟处理函数用到的数据
timer.data = 5;
// 4. 添加定时器到内核(定时器链表)
/*
* @brief 添加定时器到内核
* @param[in] timer 添加的定时器
*/
add_timer(&timer);
... ...
}
void __exit char_exit(void)
{
dev_t devno = MKDEV(major, minor);
device_destroy(char_demo_class, devno);
class_destroy(char_demo_class);
cdev_del(char_demo);
// 6. 删除定时器
/*
* @brief 删除定时器从内核中
* @notes 等待最后一次定时器处理函数完成之后,删除定时器,返回
* @return 一定返回 >= 0
*/
del_timer_sync(&timer);
unregister_chrdev_region(devno, 1);
}
注:以上总结部分选自韦东山群答疑助手:沈朝平《Linux驱动程序学习笔记》!非常感谢!