清单3.5 使用工作队列进行延后工作
#include <linux/workqueue.h>
struct workqueue_struct *wq;
/* Driver Initialization */
static int __init
mydrv_init(void)
{
/* ... */
wq = create_singlethread_workqueue("mydrv");
return 0;
}
/* Work Submission. The first argument is the work function, and
the second argument is the argument to the work function */
int
submit_work(void (*func)(void *data), void *data)
{
struct work_struct *hardwork;
hardwork = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
/* Init the work structure */
INIT_WORK(hardwork, func, data);
/* Enqueue Work */
queue_work(wq, hardwork);
return 0;
}
EXPORT_SYMBOL_GPL(queue_work);
下列语句可用于宣布你的模块使用GPL copyleft:
MODULE_LICENSE("GPL");
通知链
通知链(Notifier chains)可用于将状态改变信息发送给请求这些改变的代码段。与硬编码不同,notifier提供了一种在感兴趣的事件产生时获得警告的技术。Notifier的初始目的是将网络事件传递给内核中感兴趣的部分,但是现在也可用于许多其他目的。内核已经为主要的事件预先定义了notifier。这样的通知的实例包括:
(1)死亡通知。当内核触发了一个陷阱和错误(由oops、缺页或断点命中引发)时被发送。例如,如果你正在为一个医疗等级卡编写设备驱动,你可能需要注册自身接受死亡通知,这样,当内核恐慌发生时,你可以关闭医疗电子。
(2)网络设备通知。当一个网络接口卡启动和关闭的时候被发送。
(4)Internet地址通知。当侦测到网络接口卡的IP地址发送改变的时候,会发送此通知。
Notifier
的应用实例是drivers/net/wan/hdlc.c中的高级数据链路控制(HDLC)协议驱动,它会注册自己到网络设备通知链,以侦测载波状态的改变。
为了将你的代码与某通知链关联,你必须注册一个相关链的时间处理函数。当相应的事件发生时,事件ID和与通知相关的参数会传递给该处理函数。为了实现一个自定义的通知链,你必须另外实现底层结构,以便当事件被侦测到时,链会被激活。
清单3.6 通知事件处理函数
#include <linux/notifier.h>
#include <asm/kdebug.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
/* Die Notifier Definition */
static struct notifier_block my_die_notifier = {
.notifier_call = my_die_event_handler,
};
/* Die notification event handler */
int
my_die_event_handler(struct notifier_block *self,
unsigned long val, void *data)
{
struct die_args *args = (struct die_args *)data;
if (val == 1) { /* '1' corresponds to an "oops" */
printk("my_die_event: OOPs! at EIP=%lx\n", args->regs->eip);
} /* else ignore */
return 0;
}
/* Net Device notifier definition */
static struct notifier_block my_dev_notifier = {
.notifier_call = my_dev_event_handler,
};
/* Net Device notification event handler */
int my_dev_event_handler(struct notifier_block *self,
unsigned long val, void *data)
{
printk("my_dev_event: Val=%ld, Interface=%s\n", val,
((struct net_device *) data)->name);
return 0;
}
/* User-defined notifier chain implementation */
static BLOCKING_NOTIFIER_HEAD(my_noti_chain);
static struct notifier_block my_notifier = {
.notifier_call = my_event_handler,
};
/* User-defined notification event handler */
int my_event_handler(struct notifier_block *self,
unsigned long val, void *data)
{
printk("my_event: Val=%ld\n", val);
return 0;
}
/* Driver Initialization */
static int __init
my_init(void)
{
/* ... */
/* Register Die Notifier */
register_die_notifier(&my_die_notifier);
/* Register Net Device Notifier */
register_netdevice_notifier(&my_dev_notifier);
/* Register a user-defined Notifier */
blocking_notifier_chain_register(&my_noti_chain, &my_notifier);
/* ... */
}
通过BLOCKING_NOTIFIER_HEAD(),清单3.6中的my_noti_chain被定义为一个阻塞通知,经由对blocking_notifier_chain_register()函数的调用,它被注册。这意味着该通知事件处理函数总是在进程上下文被调用,也允许睡眠。如果你的通知处理函数允许从中断上下文调用,你应该使用ATOMIC_NOTIFIER_HEAD()定义该通知链并使用atomic_notifier_chain_register()注册它。
完成接口
一些使用场景的例子包括:
(1)你的驱动模块中包含了一个辅助内核线程。当你卸载这个模块时,在模块的代码从内核空间被移除之前,release()函数将被调用。release函数中要求内核线程杀死自身,它一直阻塞等待线程的退出。清单3.7实现了这个例子。
(2)你正在编写块设备驱动(第14章《块设备驱动》讨论)中将设备读请求排队的部分。这激活了以单独线程或工作队列方式实现的一个状态机的变更,而驱动本身想一直等到该操作完成前才执行下一次操作。drivers/block/floppy.c就是这样的一个例子。
(3)一个应用请求模拟/数字转换(ADC)驱动完成一次数据采样。该驱动初始化一个转换请求,接下来一直等待转换完成的中断产生,并返回转换后的数据。
static DECLARE_COMPLETION(my_thread_exit); /* Completion */
static DECLARE_WAIT_QUEUE_HEAD(my_thread_wait); /* Wait Queue */
int pink_slip = 0; /* Exit Flag */
/* Helper thread */
static int
my_thread(void *unused)
{
DECLARE_WAITQUEUE(wait, current);
daemonize("my_thread");
add_wait_queue(&my_thread_wait, &wait);
while (1) {
/* Relinquish processor until event occurs */
set_current_state(TASK_INTERRUPTIBLE);
schedule();
/* Control gets here when the thread is woken
up from the my_thread_wait wait queue */
/* Quit if let go */
if (pink_slip) {
break;
}
/* Do the real work */
/* ... */
}
/* Bail out of the wait queue */
__set_current_state(TASK_RUNNING);
remove_wait_queue(&my_thread_wait, &wait);
/* Atomically signal completion and exit */
complete_and_exit(&my_thread_exit, 0);
}
/* Module Initialization */
static int __init
my_init(void)
{
/* ... */
/* Kick start the thread */
kernel_thread(my_thread, NULL,
CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
/* ... */
}
/* Module Release */
static void __exit
my_release(void)
{
/* ... */
pink_slip = 1; /* my_thread must go */
wake_up(&my_thread_wait); /* Activate my_thread */
wait_for_completion(&my_thread_exit); /* Wait until my_thread
quits */
/* ... */
}
可以使用DECLARE_COMPLETION()静态地定义一个完成实例,或者使用init_completion()动态地创建之。而一个执行线索可以使用complete()或complete_all()来标识一个完成。调用者自身则通过wait_for_completion()等待完成。
在清单3.7中的my_release()函数中,在唤醒my_thread()之前,它通过pink_slip设置了一个退出请求标志。接下来,它调用wait_for_completion()等待my_thread()完成其退出。my_thread()函数醒来后,发现pink_slip被设置,它进行如下工作:
(1)向my_release()函数通知完成;
(2)杀死自身
my_thread()
使用complete_and_exit()函数原子性地完成了这2个步骤。complete_and_exit()关闭了模块退出和线程退出之间的那扇窗,而如果使用complete()和exit()函数2步操作的话,此窗口则是开放的。
在第11章中,开发一个遥测设备驱动的时候,我们会使用完成接口。
Kthread
为原始的线程创建函数添加了一层外衣由此简化了线程管理的任务。
清单3.8使用kthread接口重写了清单3.7。my_init()现在调用kthread_create()而不是kernel_thread(),你可以将线程的名字传入kthread_create(),而不再需要明确地在线程内调用daemonize()。
Kthread
允许你自由地调用内建的由完成接口所实现的退出同步机制。因此,如清单3.8中my_release()函数所为,你可以直接调用kthread_stop()而不再需要设置pink_slip、唤醒my_thread()并使用wait_for_completion()等待它的完成。相似地,my_thread()可以进行一个简洁的对kthread_should_stop()的调用以确认其是否应该退出。
/* '+' and '-' show the differences from Listing 3.7 */
#include <linux/kthread.h>
/* Assistant Thread */
static int
my_thread(void *unused)
{
DECLARE_WAITQUEUE(wait, current);
- daemonize("my_thread");
- while (1) {
+ /* Continue work if no other thread has
+ * invoked kthread_stop() */
+ while (!kthread_should_stop()) {
/* ... */
- /* Quit if let go */
- if (pink_slip) {
- break;
- }
/* ... */
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&my_thread_wait, &wait);
- complete_and_exit(&my_thread_exit, 0);
+ return 0;
}
+ struct task_struct *my_task;
/* Module Initialization */
static int __init
my_init(void)
{
/* ... */
- kernel_thread(my_thread, NULL,
- CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
SIGCHLD);
+ my_task = kthread_create(my_thread, NULL, "%s", "my_thread");
+ if (my_task) wake_up_process(my_task);
/* ... */
}
/* Module Release */
static void __exit
my_release(void)
{
/* ... */
- pink_slip = 1;
- wake_up(&my_thread_wait);
- wait_for_completion(&my_thread_exit);
+ kthread_stop(my_task);
/* ... */
}
kthread_run(my_thread, NULL, "%s", "my_thread");
错误处理助手
数个内核函数返回指针值。调用者通常将返回值与NULL对比以检查是否失败,但是它们很可能需要更多的信息以分析出确切的错误发生原因。由于内核地址有冗余比特,可以覆盖它以包含错误语义信息。一套辅助函数完成了此功能,清单3.9给出了一个简单的例子。
#include <linux/err.h>
char *
collect_data(char *userbuffer)
{
char *buffer;
/* ... */
buffer = kmalloc(100, GFP_KERNEL);
if (!buffer) { /* Out of memory */
return ERR_PTR(-ENOMEM);
}
/* ... */
if (copy_from_user(buffer, userbuffer, 100)) {
return ERR_PTR(-EFAULT);
}
/* ... */
return(buffer);
}
int
my_function(char *userbuffer)
{
char *buf;
/* ... */
buf = collect_data(userbuffer);
if (IS_ERR(buf)) {
printk("Error returned is %d!\n", PTR_ERR(buf));
}
/* ... */
}
Error returned is -12!
但是,如果collect_data()执行成功,它将返回一个数据缓冲区的指针。
再来一个例子,我们给清单3.8中的线程创建代码添加错误处理(使用IS_ERR()和PTR_ERR()):
my_task = kthread_create(my_thread, NULL, "%s", "mydrv");
+ if (!IS_ERR(my_task)) {
+ /* Success */
wake_up_process(my_task);
+ } else {
+ /* Failure */
+ printk("Error value returned=%d\n", PTR_ERR(my_task));
+ }
查看源代码
ksoftirqd
、pdflush和 khubd内核线程代码分别在kernel/softirq.c, mm/pdflush.c和 drivers/usb/core/hub.c文件中。
kernel/exit.c
可以找到daemonize(),以用户模式助手的实现见于kernel/kmod.c文件。
list
和hlist库函数位于include/linux/list.h。在整个类型中都有对它们的使用,因此在大多数子目录中,都能找到例子。其中的一个例子是include/linux/blkdev.h中定义的request_queue结构体,它存放磁盘I/O请求的链表。在第14章中我们会分析此数据结构。
查看[url]www.ussg.iu.edu/hypermail/linux/kernel/0007.3/0805.html[/url]可以跟踪到Torvalds和Andi Kleen之间关于使用hlist实现list库的利弊的争论。
内核工作队列的实现位于kernel/workqueue.c文件,为了理解工作队列的用法,可以查看drivers/net/wireless/ipw2200.c中PRO/Wireless 2200网卡驱动。
内核通知链的实现位于kernel/sys.c和include/linux/notifier.h文件。查看kernel/sched.c和include/linux/completion.h文件可以挖掘完成接口的实现机理。kernel/kthread.c包含了kthread辅助接口的源代码,include/linux/err.h则包含了错误处理接口的定义。
表3.3给出了本章中所使用的主要的数据结构及其源代码路径的总结。表3.4列出了本章中使用的主要内核编程接口及其源代码路径。
数据结构
|
路径
|
描述
|
wait_queue_t
|
include/linux/wait.h
|
|
list_head
|
include/linux/list.h
|
|
hlist_head
|
include/linux/list.h
|
用于实现哈希表的的内核结构体
|
work_struct
|
include/linux/workqueue.h
|
|
notifier_block
|
include/linux/notifier.h
|
|
completion
|
include/linux/completion.h
|
表3.4 内核编程接口总结
路径
|
描述
|
|
DECLARE_WAITQUEUE()
|
include/linux/wait.h
|
定义一个等待队列
|
add_wait_queue()
|
kernel/wait.c
|
|
remove_wait_queue()
|
kernel/wait.c
|
|
wake_up_interruptible()
|
include/linux/wait.h
kernel/sched.c
|
|
schedule()
|
kernel/sched.c
|
|
set_current_state()
|
include/linux/sched.h
|
|
kernel_thread()
|
arch/your-arch/kernel/process.c
|
|
daemonize()
|
kernel/exit.c
|
|
allow_signal()
|
kernel/exit.c
|
使能某指定信号的发起
|
signal_pending()
|
include/linux/sched.h
|
|
call_usermodehelper()
|
include/linux/kmod.h
kernel/kmod.c
|
执行一个用户模式的程序
|
Linked list library functions
|
include/linux/list.h
|
看表
3.1
|
register_die_notifier()
|
arch/your-arch/kernel/traps.c
|
注册一个
die
通知
|
register_netdevice_notifier()
|
net/core/dev.c
|
注册一个
netdevice
通知
|
register_inetaddr_notifier()
|
net/ipv4/devinet.c
|
注册一个
inetaddr
通知
|
BLOCKING_NOTIFIER_HEAD()
|
include/linux/notifier.h
|
创建一个用户自定义的阻塞性的通知
|
blocking_notifier_chain_register()
|
kernel/sys.c
|
注册一个阻塞性的通知
|
blocking_notifier_call_chain()
|
kernel/sys.c
|
将事件分发给一个阻塞性的通知链
|
ATOMIC_NOTIFIER_HEAD()
|
include/linux/notifier.h
|
创建一个原子性的通知
|
atomic_notifier_chain_register()
|
kernel/sys.c
|
注册一个原子性的通知
|
DECLARE_COMPLETION()
|
include/linux/completion.h
|
静态定义一个完成实例
|
init_completion()
|
include/linux/completion.h
|
动态定义一个完成实例
|
complete()
|
kernel/sched.c
|
宣布完成
|
wait_for_completion()
|
kernel/sched.c
|
一直等待完成实例的完成
|
complete_and_exit()
|
kernel/exit.c
|
原子性的通知完成并退出
|
kthread_create()
|
kernel/kthread.c
|
创建一个内核线程
|
kthread_stop()
|
kernel/kthread.c
|
让一个内核线程停止
|
kthread_should_stop()
|
kernel/kthread.c
|
|
IS_ERR()
|
include/linux/err.h
|
本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120805,如需转载请自行联系原作者