0%

Linux-Lab5-Deferred work

Deferred work

实验目标:

  • 了解延迟的工作(即计划在以后执行的代码)
  • 用延迟工作的常见任务的实现
  • 了解延迟工作的同步特性

延迟工作是一类内核工具,它允许人们计划代码在以后的计时器上执行,此计划代码可以在进程上下文中运行,也可以在中断上下文中运行,具体取决于延迟工作的类型

延迟工作用于补充中断处理程序功能,因为中断具有重要的要求和限制:

  • 中断处理程序的执行时间必须尽可能短
  • 在中断上下文中,我们不能使用阻塞调用

使用延迟工作 Deferred work,我们可以在中断处理程序中执行所需的最少工作,并安排中断处理程序的异步操作在以后运行并执行其余操作

  • 在中断上下文中运行的延迟工作也称为下半部分,因为它的目的是从中断处理程序(上半部分)执行其余操作

有三种典型操作可用于所有类型的延迟工作:

  • Initialization 初始化:每种类型都由一个结构描述,该结构的字段必须初始化,此时还设置了要计划的处理程序
  • Scheduling 调度:计划处理程序的执行尽快(或在超时到期后)
  • Masking or Canceling 掩蔽/取消:禁用处理程序的执行,此操作可以是同步的(这保证了处理程序在取消完成后不会运行)或异步的

延迟工作 Deferred work 的主要类型是内核线程和软件:

  • 工作队列在内核线程之上实现,任务集和计时器在软件线程之上实现
  • 下半部分处理程序是 Linux 中延迟工作的第一个实现(但与此同时,它被软中断 softirqs 所取代,基于 softirqs 又诞生了可以动态分配的 Tasklets)
  • 这就是为什么所呈现的某些函数在其名称中包含 bh(下半部分 bottom half)的原因

Softirqs

软中断 Softirqs,软中断是在编译期间静态分配的,因此不能由设备驱动使用,它们是为各种内核子系统保留的

因此,在编译时定义了固定数量的 softirq,对于当前的内核版本,我们定义了以下类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
enum {
HI_SOFTIRQ = 0, /* 运行tasklets */
TIMER_SOFTIRQ, /* 运行timers */
NET_TX_SOFTIRQ, /* 由网络子系统使用 */
NET_RX_SOFTIRQ, /* 由网络子系统使用 */
BLOCK_SOFTIRQ, /* 由IO子系统使用 */
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ, /* 运行tasklets */
SCHED_SOFTIRQ, /* 负载平衡 */
HRTIMER_SOFTIRQ, /* 实现高精度计时器 */
RCU_SOFTIRQ, /* 实施区域控制单元类型机制 */
NR_SOFTIRQS
};
  • 不同类型的 Softirqs 有不同的作用(部分类型会在后面讲到)

Softirqs 在中断上下文中运行,这意味着它们不能调用阻塞函数,如果 sofitrq 处理程序需要调用此类函数,则可以安排工作队列 work queues 来执行这些阻塞调用

Tasklets

任务集 Tasklets 是一种特殊形式的延迟工作,在中断上下文中运行,类似于 softirqs,两者主要区别在于:

  • Tasklets 可以动态分配,因此它们可以由设备驱动程序使用
  • Tasklets 由 struct tasklet 和许多其他内核结构表示,在使用之前需要对其进行初始化
1
2
3
4
void handler(unsigned long data);

DECLARE_TASKLET(tasklet, handler, data);
DECLARE_TASKLET_DISABLED(tasklet, handler, data);

如果我们想手动初始化 Tasklets,我们可以使用以下方法:

1
2
3
4
void handler(unsigned long data);

struct tasklet_struct tasklet;
tasklet_init(&tasklet, handler, data);

针对运行的 Tasklets 编程称为调度 scheduling,Tasklets 调度通过以下方式完成:

1
2
void tasklet_schedule(struct tasklet_struct *tasklet);
void tasklet_hi_schedule(struct tasklet_struct *tasklet);

可以通过以下的函数屏蔽 Tasklets:

1
2
void tasklet_enable(struct tasklet_struct * tasklet);
void tasklet_disable(struct tasklet_struct * tasklet);
  • PS:由于 Tasklets 是从 softirqs 运行的,因此无法在处理程序函数中使用阻塞调用

Timers

计时器 Timers 一种特殊类型的延迟工作:

  • 由结构定义 timer_list
  • 在中断上下文中运行,并在软件之上实现

由于 Timers 必须是原子的,所以 Timers 运行在原子上下文中,内核不能访问用户空间,而且内核是不能休眠或调度(其实 Timers 也是用 Softirqs 实现的,当然不能休眠或调度)

注册好的 Timers 是由内核子模块组织并存储的,其底层的核心功能“定时”是由内核实现,Timers 需要提供一个回调函数,该回调函数和定时器注册程序使用的是同一个线程(这也是在删除模块之前,必须停止计时器的原因)

要使用 Timers,必须首先调用如下函数:

1
2
3
4
5
#include <linux/sched.h>

void timer_setup(struct timer_list * timer,
void (*function)(struct timer_list *),
unsigned int flags);
  • 用于初始化 timer_list 的内部字段,并将函数关联为计时器处理程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
u32 flags;

#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
  • expires:运行处理程序函数 (*function)(struct timer_list *) 的时间
  • function:回调函数

使用计时器的一个常见错误是忘记关闭计时器:

  • 在删除模块之前,我们必须停止计时器
1
2
del_timer_sync(struct timer_list * timer); /* 摘除一个定时器对象,确保当函数返回时系统中没有任何处理器正在执行定时器对象上的定时器函数 */
del_timer(struct timer_list * timer); /* 从系统的定时器管理队列中摘除一个定时器对象 */
  • 如果计时器在模块被删除后过期,则处理程序函数将不再加载到内核中,并且将生成内核 oops

定时器的使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <linux/sched.h>

static struct my_device_data {
struct timer_list timer;
......
} dev;


void timer_function(struct timer_list * tl){
struct my_device_data *my_data = from_timer(my_data, tl, timer);
.....
mod_timer(&my_data->timer, jiffies + seconds * HZ);
}

static void my_access(void){
unsigned long seconds = 1;

timer_setup(&dev.timer, timer_function, 0);
mod_timer(&dev.timer, jiffies + seconds * HZ);
}

其中的 from_timer 用于从 timer_list 中获取父结构体 my_device_data

1
2
#define from_timer(var, callback_timer, timer_fieldname) \
container_of(callback_timer, typeof(*var), timer_fieldname)

Locking

为了在进程上下文(简称 “A”)中运行的代码和在 softirq 上下文(简称 “B”)中运行的代码之间保持同步,我们需要使用特殊的锁定基元:

  • 必须在 A 中使用 “停用当前处理器上的下半部分处理程序” 的自旋锁操作
  • 并且在 B 中仅使用基本的自旋锁操作

使用自旋锁可以确保我们不会在多个CPU之间进行 Race 条件竞争,而停用 softirq 可以确保我们不会在已经获得自旋锁的同一 CPU 上安排 softirq look 时出现死锁

1
2
3
4
5
6
7
8
9
10
11
12
static void my_access(void){
spin_lock_bh(&my_data->lock);
......
spin_unlock_bh(&my_data->lock);
}

static void timer_handler(struct timer_list *tl){
spin_lock(&my_data->lock);
......
spin_unlock(&my_data->lock);
mod_timer(&my_data->timer, jiffies + HZ);
}
  • 定期器的回调函数在任何时候都可能会发生(内核会直接抢占原来的进程,从而执行回调函数)
  • 如果在 my_access 中拿了自旋锁之后被 timer_handler 抢占,就会发生死锁(回调函数和定时器注册程序使用的是同一个线程)
  • 如果不在 timer_handler 中加锁,又可能会破坏共享数据(例如:在引用指针之前置空了指针)
  • 因此需要使用 spin_lock_bh 在加锁的同时禁止中断下半部(多指软中断)

我们可以使用如下函数来 禁用/启用 softirqs 处理程序:(并且由于它们运行在 softirqs 之上,所以计时器和任务集也更少)

1
2
3
4
void local_bh_disable(void); /* 禁用softirqs处理程序 */
void local_bh_enable(void); /* 启用softirqs处理程序 */
void spin_lock_bh(spinlock_t *lock); /* 禁用softirqs处理程序,添加自旋锁 */
void spin_unlock_bh(spinlock_t *lock); /* 启用softirqs处理程序,释放自旋锁 */

Workqueues

工作队列 Workqueues 用于计划在流程上下文中运行的操作,其实就是一个用于创建内核线程的接口,通过它可以创建一个“工作者线程”来专门处理中断的下半部工作

  • Workqueues 的优点就是可以使用独立于原进程的内核线程
  • 如果想要在中断处理程序中进行调度,就必须使用 Workqueues

它们工作的基本单位称为工作项 Work items,有两种类型的工作:

  • struct work_struct:它计划稍后运行的任务
  • struct delayed_work:它计划任务在至少给定的时间间隔后运行

在使用工作项之前,必须初始化工作项,可以使用两种类型的宏:

  • 一种是同时声明和初始化工作项 Work items
  • 另一种是仅初始化工作项 Work items
1
2
3
4
5
6
7
#include <linux/workqueue.h>

DECLARE_WORK(name , void (*function)(struct work_struct *));
DECLARE_DELAYED_WORK(name, void(*function)(struct work_struct *));

INIT_WORK(struct work_struct *work, void(*function)(struct work_struct *));
INIT_DELAYED_WORK(struct delayed_work *work, void(*function)(struct work_struct *));

一旦声明和初始化工作队列,我们可以使用以下函数来调度任务:

1
2
bool schedule_work(struct work_struct *work);
bool schedule_delayed_work(struct delayed_work *work, unsigned long delay);

可以用如下函数取消工作项:

1
2
int cancel_work_sync(struct delayed_work *work);
int cancel_delayed_work_sync(struct delayed_work *work);

可以通过以下方式等待工作队列完成运行其所有工作项:

1
void flush_scheduled_work(void);

当然以上这些函数都是针对某个工作项 Work items 而言

工作队列由结构 workqueue_struct 表示,可以使用以下这些函数创建新的工作队列:

1
2
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);

要在新队列中添加任务(某个工作项),使用如下函数:

1
2
3
4
int queue_work(struct workqueue_struct * queue, struct work_struct *work);

int queue_delayed_work(struct workqueue_struct *queue,
struct delayed_work * work , unsigned long delay);

等待所有工作项完成调用:

1
void flush_workqueue(struct worksqueue_struct * queue);

销毁工作队列:

1
void destroy_workqueue(struct workqueue_struct *queue);

Kernel threads

内核线程是工作队列机制的基础,从本质上讲,内核线程是仅在内核模式下运行的线程,没有用户地址空间或其他用户属性

创建内核线程的函数:(但是不会直接运行)

1
2
3
4
#include <linux/kthread.h>

struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data, const char namefmt[], ...);
  • threadfn:将由内核线程运行的函数
  • data:要发送到函数的参数
  • namefmt:表示内核线程名称

启动内核线程:

1
int wake_up_process(struct task_struct *p);

创建和运行内核线程:

1
2
struct task_struct * kthread_run(int (*threadfn)(void *data)
void *data, const char namefmt[], ...);

内核线程的终止是在内核线程中运行的函数中自愿完成的,通过调用如下函数:

1
fastcall NORET_TYPE void do_exit(long code);

内核线程处理程序的大多数实现都使用相同的模型,建议开始使用相同的模型以避免常见错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <linux/kthread.h>

DECLARE_WAIT_QUEUE_HEAD(wq); /* 初始化等待队列 */

// list events to be processed by kernel thread
struct list_head events_list;
struct spin_lock events_lock;

// structure describing the event to be processed
struct event {
struct list_head lh;
bool stop;
//...
};

struct event* get_next_event(void)
{
struct event *e;
spin_lock(&events_lock);
e = list_first_entry(&events_list, struct event*, lh);
if (e)
list_del(&e->lh);
spin_unlock(&events_lock);
return e
}

int my_thread_f(void *data)
{
struct event *e;
while (true) {
wait_event(wq, (e = get_next_event));
/* Event processing */
if (e->stop)
break;
}
do_exit(0);
}

/* start and start kthread */
kthread_run(my_thread_f, NULL, "%skthread%d", "my", 0);

使用上面的模板,内核线程请求可以通过以下方式发出:

1
2
3
4
5
6
7
void send_event(struct event *ev)
{
spin_lock(&events_lock);
list_add(&ev->lh, &events_list);
spin_unlock(&events_lock);
wake_up(&wq);
}

Exercises

要解决练习,您需要执行以下步骤:

  • 从模板准备 skeletons
  • 构建模块
  • 将模块复制到虚拟机
  • 启动 VM 并在 VM 中测试模块
1
2
3
LABS=deferred_work make skels
make build
make copy

1.Timer 完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
* Deferred Work
*
* Exercise #1, #2: simple timer
*/

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>

MODULE_DESCRIPTION("Simple kernel timer");
MODULE_AUTHOR("SO2");
MODULE_LICENSE("GPL");

#define TIMER_TIMEOUT 1

static struct timer_list timer;

static void timer_handler(struct timer_list *tl)
{
/* TODO 1: print a message */
static int message = 0;
pr_info("message :%d\n",message++);
/* TODO 2: rechedule timer */
mod_timer(&timer, jiffies + TIMER_TIMEOUT * HZ);
}

static int __init timer_init(void)
{
pr_info("[timer_init] Init module\n");

/* TODO 1: initialize timer */
timer_setup(&timer, timer_handler, 0);
/* TODO 1: schedule timer for the first time */
mod_timer(&timer, jiffies + TIMER_TIMEOUT * HZ);

return 0;
}

static void __exit timer_exit(void)
{
pr_info("[timer_exit] Exit module\n");
/* TODO 1: cleanup; make sure the timer is not running after we exit */
del_timer_sync(&timer);
}

module_init(timer_init);
module_exit(timer_exit);
  • 结果:
1
2
3
4
5
6
7
8
root@qemux86:~/skels/deferred_work/1-2-timer# insmod timer.ko                   
timer: loading out-of-tree module taints kernel.
[timer_init] Init module
root@qemux86:~/skels/deferred_work/1-2-timer# message :0
message :1
message :2
message :3
message :4
  • 在定时器处理程序中还可以调用定时器,这样就可以实现定时循环

2.Deferred 完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/*
* SO2 - Lab 6 - Deferred Work
*
* Exercises #3, #4, #5: deferred work
*
* Code skeleton.
*/

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/sched/task.h>
#include "../include/deferred.h"

#define MY_MAJOR 42
#define MY_MINOR 0
#define MODULE_NAME "deferred"

#define TIMER_TYPE_NONE -1
#define TIMER_TYPE_SET 0
#define TIMER_TYPE_ALLOC 1
#define TIMER_TYPE_MON 2

MODULE_DESCRIPTION("Deferred work character device");
MODULE_AUTHOR("SO2");
MODULE_LICENSE("GPL");

struct mon_proc {
struct task_struct *task;
struct list_head list;
};

static struct my_device_data {
struct cdev cdev;
/* TODO 1: add timer */
struct timer_list timer;
/* TODO 2: add flag */
unsigned int flag;
/* TODO 3: add work */
struct work_struct work;
/* TODO 4: add list for monitored processes */
struct list_head list;
/* TODO 4: add spinlock to protect list */
spinlock_t lock;
} dev;

static void alloc_io(void)
{
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(5 * HZ);
pr_info("Yawn! I've been sleeping for 5 seconds.\n");
}

static struct mon_proc *get_proc(pid_t pid)
{
struct task_struct *task;
struct mon_proc *p;

rcu_read_lock();
task = pid_task(find_vpid(pid), PIDTYPE_PID);
rcu_read_unlock();
if (!task)
return ERR_PTR(-ESRCH);

p = kmalloc(sizeof(*p), GFP_ATOMIC);
if (!p)
return ERR_PTR(-ENOMEM);

get_task_struct(task);
p->task = task;

return p;
}


/* TODO 3: define work handler */
static void work_handler(struct work_struct *work)
{
alloc_io();
}

#define ALLOC_IO_DIRECT
/* TODO 3: undef ALLOC_IO_DIRECT*/
#undef ALLOC_IO_DIRECT

static void timer_handler(struct timer_list *tl)
{
/* TODO 1: implement timer handler */
struct my_device_data *my_data = from_timer(my_data, tl, timer);
pr_info("[timer_handler] pid = %d, comm = %s\n",
current->pid, current->comm);
/* TODO 2: check flags: TIMER_TYPE_SET or TIMER_TYPE_ALLOC */
switch (my_data->flag) {
case TIMER_TYPE_SET:
break;
case TIMER_TYPE_ALLOC:
// alloc_io();
/* TODO 3: schedule work */
schedule_work(&my_data->work);
break;
/* TODO 4: iterate the list and check the proccess state */
case TIMER_TYPE_MON:
{
struct mon_proc *p, *n;
spin_lock(&my_data->lock);
/* TODO 4: if task is dead print info ... */
/* TODO 4: ... decrement task usage counter ... */
/* TODO 4: ... remove it from the list ... */
/* TODO 4: ... free the struct mon_proc */
list_for_each_entry_safe(p, n, &my_data->list, list){
if (p->task->state == TASK_DEAD) {
pr_info("task %s (%d) is dead\n", p->task->comm,
p->task->pid);
put_task_struct(p->task);
list_del(&p->list);
kfree(p);
}
}
spin_unlock(&my_data->lock);

mod_timer(&my_data->timer, jiffies + HZ);
break;
}
}
}

static int deferred_open(struct inode *inode, struct file *file)
{
struct my_device_data *my_data =
container_of(inode->i_cdev, struct my_device_data, cdev);
file->private_data = my_data;
pr_info("[deferred_open] Device opened\n");
return 0;
}

static int deferred_release(struct inode *inode, struct file *file)
{
pr_info("[deferred_release] Device released\n");
return 0;
}

static long deferred_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct my_device_data *my_data = (struct my_device_data*) file->private_data;

pr_info("[deferred_ioctl] Command: %s\n", ioctl_command_to_string(cmd));

switch (cmd) {
case MY_IOCTL_TIMER_SET:
/* TODO 2: set flag */
my_data->flag = TIMER_TYPE_SET;
/* TODO 1: schedule timer */
mod_timer(&dev.timer,jiffies + arg * HZ);
break;
case MY_IOCTL_TIMER_CANCEL:
/* TODO 1: cancel timer */
del_timer_sync(&dev.timer);
break;
case MY_IOCTL_TIMER_ALLOC:
/* TODO 2: set flag and schedule timer */
my_data->flag = TIMER_TYPE_ALLOC;
mod_timer(&dev.timer,jiffies + arg * HZ);
break;
case MY_IOCTL_TIMER_MON:
{
/* TODO 4: use get_proc() and add task to list */
struct mon_proc *p = get_proc(current->pid);
/* TODO 4: protect access to list */
spin_lock_bh(&my_data->lock);
list_add(&p->list,&my_data->list);
spin_unlock_bh(&my_data->lock);
/* TODO 4: set flag and schedule timer */
my_data->flag = TIMER_TYPE_MON;
mod_timer(&my_data->timer,jiffies + arg * HZ);
break;
}
default:
return -ENOTTY;
}
return 0;
}

struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = deferred_open,
.release = deferred_release,
.unlocked_ioctl = deferred_ioctl,
};

static int deferred_init(void)
{
int err;

pr_info("[deferred_init] Init module\n");
err = register_chrdev_region(MKDEV(MY_MAJOR, MY_MINOR), 1, MODULE_NAME);
if (err) {
pr_info("[deffered_init] register_chrdev_region: %d\n", err);
return err;
}

/* TODO 2: Initialize flag. */
dev.flag = TIMER_TYPE_NONE;
/* TODO 3: Initialize work. */
INIT_WORK(&dev.work, work_handler);
/* TODO 4: Initialize lock and list. */
spin_lock_init(&dev.lock);
INIT_LIST_HEAD(&dev.list);

cdev_init(&dev.cdev, &my_fops);
cdev_add(&dev.cdev, MKDEV(MY_MAJOR, MY_MINOR), 1);

/* TODO 1: Initialize timer. */
timer_setup(&dev.timer,timer_handler,0);
return 0;
}

static void deferred_exit(void)
{
struct mon_proc *p, *n;

pr_info("[deferred_exit] Exit module\n" );

cdev_del(&dev.cdev);
unregister_chrdev_region(MKDEV(MY_MAJOR, MY_MINOR), 1);

/* TODO 1: Cleanup: make sure the timer is not running after exiting. */
del_timer_sync(&dev.timer);
/* TODO 3: Cleanup: make sure the work handler is not scheduled. */
flush_scheduled_work();
/* TODO 4: Cleanup the monitered process list */
list_for_each_entry_safe(p, n, &dev.list, list) {
/* TODO 4: ... decrement task usage counter ... */
/* TODO 4: ... remove it from the list ... */
/* TODO 4: ... free the struct mon_proc */
put_task_struct(p->task);
list_del(&p->list);
kfree(p);
}
}

module_init(deferred_init);
module_exit(deferred_exit);
  • 结果:
1
2
3
4
5
6
7
8
9
10
11
root@qemux86:~/skels/deferred_work/3-4-5-deferred# insmod ./kernel/deferred.ko  
deferred: loading out-of-tree module taints kernel.
[deferred_init] Init module
root@qemux86:~/skels/deferred_work/3-4-5-deferred# mknod /dev/deferred c 42 0
root@qemux86:~/skels/deferred_work/3-4-5-deferred# ./user/test a 3
[deferred_open] Device opened
Allocate memory after 3 seconds
[deferred_ioctl] Command: Allocate memory
[deferred_release] Device released
root@qemux86:~/skels/deferred_work/3-4-5-deferred# [timer_handler] pid = 0, com0
BUG: scheduling while atomic: swapper/0/0/0x00000102
  • 驱动程序导致错误,因为在原子上下文中调用了阻塞函数 alloc_io(计时器处理程序运行中断上下文)
  • 但是 schedule_work(&my_data->work) 中,使用工作队列新开一个内核线程来运行 alloc_io 就不会报错(这也是工作队列的优势)
1
2
3
4
5
6
7
8
9
root@qemux86:~/skels/deferred_work/3-4-5-deferred# ./user/test p 3              
[deferred_open] Device opened
Monitor PID 3.
[deferred_ioctl] Command: Monitor pid
[deferred_release] Device released
root@qemux86:~/skels/deferred_work/3-4-5-deferred# [timer_handler] pid = 0, com0
task test (239) is dead
[timer_handler] pid = 0, comm = swapper/0
[timer_handler] pid = 0, comm = swapper/0
  • 正常打印进程信息

3.Kthread 完整代码:

  • 实现一个简单的模块,该模块会创建用于显示当前进程标识符的内核线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*
* SO2 - Lab 6 - Deferred Work
*
* Exercise #6: kernel thread
*/

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <asm/atomic.h>
#include <linux/kthread.h>

MODULE_DESCRIPTION("Simple kernel thread");
MODULE_AUTHOR("SO2");
MODULE_LICENSE("GPL");

wait_queue_head_t wq_stop_thread;
atomic_t flag_stop_thread;
wait_queue_head_t wq_thread_terminated;
atomic_t flag_thread_terminated;

int my_thread_f(void *data)
{
pr_info("[my_thread_f] Current process id is %d (%s)\n",
current->pid, current->comm);
/* TODO: Wait for command to remove module on wq_stop_thread queue. */
wait_event_interruptible(wq_stop_thread,atomic_read(&flag_stop_thread)!=0);
/* TODO: set flag to mark kernel thread termination */
atomic_set(&flag_thread_terminated,1);
/* TODO: notify the unload process that we have exited */
wake_up_interruptible(&wq_thread_terminated);
pr_info("[my_thread_f] Exiting\n");
do_exit(0);
}

static int __init kthread_init(void)
{
pr_info("[kthread_init] Init module\n");
/* TODO: init the waitqueues and flags */
init_waitqueue_head(&wq_stop_thread);
init_waitqueue_head(&wq_thread_terminated);
atomic_set(&flag_stop_thread,0);
atomic_set(&flag_thread_terminated,0);
/* TODO: create and start the kernel thread */
struct task_struct * kt = kthread_create(my_thread_f,NULL,"yhellow");
wake_up_process(kt);

return 0;
}

static void __exit kthread_exit(void)
{
/* TODO: notify the kernel thread that its time to exit */
atomic_set(&flag_stop_thread,1);
wake_up_interruptible(&wq_stop_thread);
/* TODO: wait for the kernel thread to exit */
wait_event_interruptible(wq_thread_terminated,atomic_read(&flag_thread_terminated) != 0);
pr_info("[kthread_exit] Exit module\n");
}

module_init(kthread_init);
module_exit(kthread_exit);
  • 结果:
1
2
3
4
5
6
7
root@qemux86:~/skels/deferred_work/6-kthread# insmod kthread.ko                 
kthread: loading out-of-tree module taints kernel.
[kthread_init] Init module
[my_thread_f] Current process id is 258 (yhellow)
root@qemux86:~/skels/deferred_work/6-kthread# rmmod kthread.ko
[my_thread_f] Exiting
[kthread_exit] Exit modul
  • 必须等到内核线程停止以后,内核模块才可以停止
  • 由于内核线程和内核模块是同时运行的,为了使内核模块先停止,做了以下操作:
    • kthread_exit 执行之前,内核线程会因为 flag_stop_thread = 0 被添加到等待队列中
    • kthread_exit 执行的过程中设置 flag_stop_thread = 1,同时唤醒内核线程(如果不设置 flag_stop_thread = 1 而直接唤醒,就会导致死锁),然后因为 flag_thread_terminated = 0 被添加到等待队列中
    • my_thread_f 中设置 flag_thread_terminated = 1,同时唤醒内核模块
    • 由于唤醒需要时间:内核线程先关闭,内核模块后关闭
  • 本实验只涉及的“单个内核线程”的情况,如果需要处理多个内核线程,则可以使用之前给出的模板