全部博文(71)
分类: LINUX
2009-11-07 10:06:33
来看下另外一个很有用的链表函数list_for_each_entry(),通过这个宏遍历变的更简单、更有可读性因为你不用在循环里面使用list_entry(). 如果你要在循环里面删除元素就使用list_for_each_entry_safe()吧。可以在上面代码中替换下面片段:
1 while(!list_empty(&mydrv_wq.mydrv_worklist)) {
2 mydrv_work = list_entry(mydrv_wq.mydrv_worklist.next, struct_mydrv_work, mydrv_workitem);
3 /*....*/
4 }
为:
struct _mydrv_work *temp;
list_for_each_safe(mydrv_work, temp, &mydrv_wq.mydrv_worklist, mydrv_workitem) {
/*...*/
}
你不能使用list_for_each_entry()因为你需要删除指向mydrv_work的条目(entry)。list_for_each_entry_safe()使用临时变量temp作为第二个参数来保存下个entry的地址来解决了这个问题。
哈希链表
前面提到的双链表在实现哈希表形式的链表并不是很理想。这是因为哈希表仅仅需要包含一个指针的链表表。为减少应用程序的过高内存,内核提供了哈希链表。和链表不同,哈希表有分开的定义:
struct hlist_head{
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **prev;
};
为配合单指针哈希链表头格式,结点也维护了指向前面结点的指针地址,而不仅仅是它自己的指针。
哈希表使用hlist_heads数组实现,每个hlist_head是一个hlist_node双链表。哈希功能用来在hlist_head数组中寻址索引,当这完成后,你可以使用哈希表助手函数(定义在include/linux/list.h)来对选择的hlist_node操作。fs/dcache.c中的目录缓存dcache实现就是个例子。
工作队列
工作队列是内核里面推迟工作的一种方式。软中断(softirq)和小任务(tasklet)是内核里面另外两种推迟工作的机制。推迟工作在很多情况下很有用,包括:
1、为回应一个错误中断触发网络适配器重启.
2、诸如同步磁盘缓冲的文件系统任务.
3、发送命令给磁盘并走通(follow through)存储协议状态机.
工作队列助手暴露了两个接口结构给用户:workqueue_struct和work_struct。以下是使用工作队列的步骤:
1、为一个或多个关联内核线程创建一个工作队列(workqueue_struct)。使用create_singlethread_workqueue()创建一个内核线程来为workqueue_struct服务。使用create_workqueue()为每个CPU创建一个工作线程。内核也有缺省的每个CPU工作线程(event/n,n是CPU编号),可以用来分时共享而不用请求一个独立的工作线程。如果你没有专注的工作线程可能会招致性能损失,这取决于你的应用程序。
2、创建一个工作元素(work_struct). 使用INIT_WORK()来初始化work_struct,它用你的工作函数的地址和参数来产生。
3、提交工作元素到工作队列。一个work_struct可以使用queue_work()提交到一个独立队列或使用schedule_work()提交给缺省的内核工作线程。
让我们用工作队列接口的优势来重写以前的程序。如下,整个内核线程以及自旋锁、等待队列在工作队列接口中都消失了。如果你使用缺省内核工作线程,对create_singlethread_workqueue()的调用也会消失。
#include
struct workqueue_struct *wq;
/*设备初始化*/
static int __init mydrv_init(void)
{
/*...*/
wq = create_singlethread_workqueue("mydrv");
return 0;
}
/*提交工作。第一个参数是工作函数,第二个是工作函数的参数*/
int submit_work(void (*func)(void *data), void *data)
{
struct work_struct *hardwork;
hardwork = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
INIT_WORK(hardwork, func, data);
/*工作进队列*/
queue_work(wq, hardwork);
return 0;
}
如果你使用工作队列将会得到链接错误除非声明你的模块授权是GPL。这是因为内核仅仅导出这些函数到GPL授权代码中。如果你查看内核工作队列的实现,你会看到语句中的约束表达:
EXPORT_SYMBOL_GPL(queue_work);
你可以这样声明你的模块为GPL授权:
MODULE_LICENSE("GPL");
通知者链
通知者链用来发送状态改变信息给请求他们的代码区。不像硬编码机制,通知者提供了多重技巧使得有意思事件产生时得到提醒。通知者原来用来传递网络事件给内核中关注部分,现在用于很多其他目的。内核为重要事件实现了预定义的通知者,这些提醒例子如下:
死亡提醒, 当内核函数触发了一个陷阱或违例错误发送,由oops页错误或断点命中引发。例如为一个医学级别卡写设备驱动,你可能想注册自己给死亡提醒者,以在内核崩溃发生时关闭医疗电子信号。
网路设备提醒:网络接口启动或关闭时产生。
CPU频率提醒:当处理器频率跃变时分发出去。
因特网地址提醒:当网络接口的IP地址发生变化被检测时发送。
一个用户提醒的例子就是高级数据链路控制(HDLC)协议驱动drivers/net/wan/hdlc.c,它注册自己给网络设备通知链以察觉载波变化。
要附加你的代码到通知链,你要注册一个时间处理函数给相关的链。当关注的事件产生时,一个事件识别符和一个通知特定的参数作为参数传递给处理函数。要定义一个定制通知链,你要另外实现事件检测到时引发链的基础代码。
下面代码包含了使用预定义和用户定义的通知者例子,下面包含了使用的通知链和产生事件的简短描叙。
Die Notifier Chain(die_chain) my_die_event_handler()使用regist_die_notifier()附加给死亡通知链,die_chain。要触发my_die_event_handler(),在你的代码某处引入一个不合理解引用,像下面的:
int *q = 0;
*q = l;
当这个代码片段执行时,my_die_event_handler()得到调用,你可以看到下面这样的消息:
my_die_event_handler: oops! at EIP=f00350e7
死亡事件通知传die_args结构体给已经注册的事件处理函数。这个参数在当错误发生时包含了指向regs结构体的指针,该结构体带有处理器寄存器的快照。my_die_event_handler()打印了指令指针寄存器的内容。
Netdevice Notifier Chain(netdev_chain): my_dev_event_handler()使用register_netdevice_notifer()附加到网络设备通知链netdev_chain。你可以改变网络接口状态如以太网(ethX)和环回(lo)来产生这个事件: bash>ifconfig eth0 up 这样导致my_dev_event_handler()的执行。一个包含网络接口名字的指向struct net_device的指针作为参数传给该处理函数。 my_dev_event_handler()使用这个信息产生下列信息:
my_dev_event_handler: Val=1, Interface=eth0
Val=1对应于定义在include/linux/notifier.h中的NETDEV_UP事件。
User-Defined Notifier Chain:程序也实现了用户定义通知链my_noti_chain。假设你想一个事件在用户读进程文件系统中一个特定文件时产生。添加下列语句到关联的procfs读程序中:
blocking_notifier_call_chain(&my_noti_chain, 100, NULL);
这将导致你读/proc文件时my_event_handler()的执行,并产生下面信息:
my_event_handler: Val=100
Val包含了产生的事件标识符100,函数参数没有使用到。
#include
#include
#include
#include
/* 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);
/* ... */
}
当你的模块从内核中释放时,你得在通知链里面反注册事件处理函数。例如,如果你卸载代码后启动和关闭网络接口,就会得到oops,除非在模块的release函数中执行unregister_netdevice_notifier(&my_dev_notifier)。这是因为通知链继续认为处理函数代码是合理的,尽管它就被拉出了内核。
my_noti_chain通过使用BLOCKING_NOTIFIER_HEAD()声明为阻塞通知并通过blocking_notifier_chain_register()注册。这意味着通知处理函数经常在进程上下文中被调用。所以处理函数my_event_handler()可以进入休眠。如果你的通知处理函数可以在中断上下文中调用,使用ATOMIC_NOTIFIER_HEAD()声明并且用atomic_notifier_chain_register()注册它。
早于2.6.17的内核只支持一个通用通知链。通知注册函数notifier_chain_register()使用自旋锁被内部保护着,但是通知链分发事件到通知处理函数(notifier_call_chain())的代码确实没有加锁。之所以没有锁是基于处理函数进入休眠、运行时反注册自己和在中断上下文中得到调用的可能性。但没有锁的实现引入了竞争条件。新的通知API是基于原有接口构建并意图克服它的限制。
完成量接口
内核的一些部分初始化一定的行为为独立的执行线程并等待它们执行结束。完成量接口是实现这样代码模式的一个最高效并且简单的方式。
使用的例子包含如下:
1、你的驱动模块有一个内核线程来辅助。如果你rmmod模块,在从内核空间删除模块代码前调用release()函数。release程序请求线程杀死自己并阻塞直到线程完成它的退出。
2、你写了一个块设备驱动程序,对设备的读请求都排队处理。这触发了一个由一个独立的线程或工作队列实现的状态机改变。驱动想在进行另一个活动前等待直到操作完成。查看drivers/block/floppy.c作为例子。
3、一个应用程序请求A/D转换器驱动以得到数据样本。驱动初始化一个转换请求、等待直到完成转换的中断信号的到来,并返回数据。
使用完成量来同步的程序:
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()等待完成。上面代码中my_release()通过唤醒my_thread()前设立pink_slip发出退出请求标志。接着调用wait_for_completion()等待直到my_thread()完成退出。my_thread()在它自己一方唤醒后查找pink_slip设置,接着做如下事情:
1、发送完成信号给my_release()
2、杀死自己
my_thread()使用complete_and_exit()自动完成这两个步骤。使用complete_and_exit()关闭模块退出和线程退出之间打开的窗口(如果你分开调用了complete()和exit()的话)。
kthread助手
kthread助手在原始的线程创建程序外面加了个“外套”简化了线程管理的任务。下面的代码使用kthread助手接口重写了刚才的程序。my_init()现在使用kthread_create()而不是kernel_thread().你可以传递线程名字给kthread_create()而不用在线程里面显式的调用daemonize().
kthread接口为你提供了自由访问内置的使用完成量接口实现的同步机制,所以my_release()直接调用kthread_stop()而不用设置pink_slip和使用wait_for_completion()来等待完成。同样的my_thread()可以做一个灵巧的kthread_should_stop()调用来检查应该调用它一天。
使用kthread助手的同步:
/* '+' and '-' show the differences from Listing 3.7 */
#include
/* 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_create()来创建线程并用wake_up_process()来激活外,你也可以使用下面的单个调用:
kthread_run(my_thread, NULL, "%s", "my_thread");
错误处理
一些内核函数返回指针值。调用者通常通常比较返回值和NULL来检查失败,往往需要更多信息来描述正确的错误。因为内核地址有多余的比特,可以用来编码错误语义,这可以借助助手程序集来做到。
使用错误处理帮手:
#include
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));
}
/* ... */
}
如果collect_data()里的kmalloc()失败,你将得到下面信息:
Error returned is -12!
如果collect_data()成功了,它返回一个合理的指向数据缓冲的指针。作为另外的例子,我们使用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、qdflush和khubd内核线程分布在kernel/softirq.c、mm/pdflush.c,drivers/usb/core/hub.c中。
daemonize()函数可以在kernel/exit.c中找到。在kernel/kmod.c中可以找到用户模式助手的实现。
链表和哈希链表库程序驻留在include/linux/list.h中。他们用在所有内核中,所以你可以在大多数的子目录中找到使用例子。一个例子是定义在include/linux/blkdev.h中的request_queue结构体,它持有一个磁盘I/O请求的链表。
内核工作队列的实现在kernel/workqueue.c中。要理解真实世界中的工作队列的使用,查看PRO/Wireless 2200网络驱动drivers/net/wireless/ipw2200.c.
内核通知链的实现在kernel/sys.c和include/linux/notifier.h中. kernel/sched.c和include/linux/completion.h有完成量接口的详情. kernel/kthread.c包含kthread助手的代码. include/linux/err.h有错误处理助手的定义.