deferred work

背景信息

延期工作是内核的一个特色,用于在随后的某个时间执行某个代码。被预定的代码可以运行在进程上下文或者中断上下文。延期工作被用于完成中断处理功能因为中断拥有重要的要求和限制如下:

  • 中断处理程序的执行时间要尽可能地小
  • 在中断上下文中不能够使用导致阻塞的调用

使用延期工作,我们可以保证在中断处理程序中对其他的要求尽量低,并且在随后的时间之后剩下的操作.

延期工作可以运行在中断上下文中,这被理解为下半段.因为他的作用是执行除了上半部分的剩下的其余的操作.

Timers是延期工作的另一种类型,可以被用于为将来要执行的程序定时.内核线程本身并不是延期工作,但是可以被用于实现延期工作的机制.内核线程是处理一些发起可能会引起阻塞的调用的工作者.

有三种经典的操作被用于所有类型的延期工作.

  • Intialization.初始化.每个被structure描述的类型都需要初始化.
  • Scheduling. handler的执行尽可能快或者在一个特定的时间之后.
  • Masking或者Canceling.禁止handler的执行.可以是同步或者异步的.

延期工作的主要类型是内核线程和softirqs.工作队列被实现在内核线程的顶层.中断处理程序的下半段是延期工作的首次实现,但是同时它被softirqs代替.这就死一些方法的名字中包含bh的原因.

Softirqs

softirqs不能够被设备驱动使用,他们被保留用于多种多样的内核子系统。因为固定数目的softirqs被在编译时被定义。

  • HI_SOFTIRQ and TASKLET_SOFTIRQ - running tasklets
  • TIMER_SOFTIRQ - running timers
  • NET_TX_SOFIRQ and NET_RX_SOFTIRQ - used by the networking subsystem
  • BLOCK_SOFTIRQ - used by the IO subsystem
  • BLOCK_IOPOLL_SOFTIRQ - used by the IO subsystem to increase performance when the iopoll handler is invoked;
  • SCHED_SOFTIRQ - load balancing
  • HRTIMER_SOFTIRQ - implementation of high precision timers
  • RCU_SOFTIRQ - implementation of RCU type mechanisms

每个类型都用于某个特殊的目的:
HI_SOFTIRQ and TASKLET_SOFTIRQ - running tasklets
TIMER_SOFTIRQ - running timers
NET_TX_SOFIRQ and NET_RX_SOFTIRQ - used by the networking subsystem
BLOCK_SOFTIRQ - used by the IO subsystem
BLOCK_IOPOLL_SOFTIRQ - used by the IO subsystem to increase performance when the iopoll handler is invoked;
SCHED_SOFTIRQ - load balancing
HRTIMER_SOFTIRQ - implementation of high precision timers
RCU_SOFTIRQ - implementation of RCU type mechanisms [1]

Softirqs运行在中断上下文当中,这意味着它们不能调用导致阻塞的方法。如果softirq处理器中调用了可能导致阻塞的方法,那么工作队列将会计划去执行这些导致阻塞的调用。

Tasklets

tasklet是运行在中断上下文中的延期工作的一种特殊的形式,就像是softirq一样。在softirqs和tasklets之间主要的不同是tasklets可以被动态地进行分配,所以它可以被应用到设备驱动程序当中。tasklet的代表是struct tasklet,并且在使用它之前需要首先进行初始化。在初始化之前的tasklet可以被定义为如下这样:

void handler(unsigned long data);

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

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

如果我们想要手动初始化tasklet,那么我们可以如下这样做:

void handler(unsigned long data);

struct tasklet_struct tasklet;

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

tasklet_init(&tasklet, handler, data);
void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
{
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}

当被执行的时候,dada会被送到handler当中。
讲tasklet运行其阿里需要调用schedule,tasklets运行自softirqs

void tasklet_schedule(struct tasklet_struct *tasklet);

void tasklet_hi_schedule(struct tasklet_struct *tasklet);

当使用tasklet_schedule,TASKLET_SOFTIRQ softirq 被计划安排并且所有的tasklet被计划运行。对于tasklet_hi_schedule,HI_SOFTIRQ softirq被定时.

Tasklets可以别掩盖,下面的方法可以使用

void tasklet_enable(struct tasklet_struct * tasklet );
static inline void tasklet_enable(struct tasklet_struct *t)
{
	smp_mb__before_atomic();
	atomic_dec(&t->count);
}
void tasklet_disable(struct tasklet_struct * tasklet );
Timers

一个特殊类型的延期工作,经常被使用,就是timers。他们被struct timer_list定义。运行在中断上下文并softirq实现。在使用timer之前,调用timer_setup()来完成初始化的工作。

struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct list_head entry;
	unsigned long expires;
	struct tvec_base *base;

	void (*function)(unsigned long);
	unsigned long data;

	int slack;

#ifdef CONFIG_TIMER_STATS
	int start_pid;
	void *start_site;
	char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};
#include <linux / sched.h>

void timer_setup(struct timer_list * timer,
                 void (*function)(struct timer_list *),
                 unsigned int flags);

作为参数的function是一个handler

schedule a timeer is done with mod_timer

int mod_timer(struct timer_list *timer, unsigned long expires);

/**
 * mod_timer - modify a timer's timeout
 * @timer: the timer to be modified
 * @expires: new timeout in jiffies
 *
 * mod_timer() is a more efficient way to update the expire field of an
 * active timer (if the timer is inactive it will be activated)
 *
 * mod_timer(timer, expires) is equivalent to:
 *
 *     del_timer(timer); timer->expires = expires; add_timer(timer);
 *
 * Note that if there are multiple unserialized concurrent users of the
 * same timer, then mod_timer() is the only safe way to modify the timeout,
 * since add_timer() cannot modify an already running timer.
 *
 * The function returns whether it has modified a pending timer or not.
 * (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an
 * active timer returns 1.)
 */
int mod_timer(struct timer_list *timer, unsigned long expires)
{
	expires = apply_slack(timer, expires);

	/*
	 * This is a common optimization triggered by the
	 * networking code - if the timer is re-modified
	 * to be the same thing then just return:
	 */
	if (timer_pending(timer) && timer->expires == expires)
		return 1;

	return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
}

timers的时间单位是jiffie。jiffie依赖于具体的平台。为了在jiffie和second之间进行转换,可以按照如下的公式

jiffies_value = seconds_value * HZ ;
seconds_value = jiffies_value / HZ ;

初始化并且定时一秒的常用的代码顺序是

#include <linux/sched.h>

void timer_function(struct timer_list *);

struct timer_list timer ;
unsigned long seconds = 1;

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

如何停止它呢?

int del_timer(struct timer_list *timer);
int del_timer_sync(struct timer_list *timer);

为了使在进程上下文(A)中运行的代码与在softirq上下文(B)中运行的代码之间同步,我们需要使用特殊的锁定原语。 在(A)中,我们必须使用自旋锁操作来增强当前处理器下半部处理程序的停用,而在(B)中,仅使用基本自旋锁操作。 使用自旋锁可确保在多个CPU之间不会出现竞争,而停用softirq则可确保我们不会在已获得自旋锁的同一CPU上调度softirq的死锁。

我们可以使用local_bh_disable()和local_bh_enable()来禁用和启用softirqs处理程序(并且由于它们运行在softirqs之上,因此它们还包括计时器和tasklet):

void local_bh_disable(void);
void local_bh_enable(void);

允许嵌套调用,仅当所有local_bh_disable()调用均已由local_bh_enable()调用进行补充时,才可以实际重新激活softirqs:

/* We assume that softirqs are enabled */
local_bh_disable();  /* Softirqs are now disabled */
local_bh_disable();  /* Softirqs remain disabled */

local_bh_enable();  /* Softirqs remain disabled */
local_bh_enable();  /* Softirqs are now enabled */

Attention
These above calls will disable the softirqs only on the local processor and they are usually not safe to use, they must be complemented with spinlocks.

Most of the time device drivers will use special versions of spinlocks calls for synchronization like spin_lock_bh() and spin_unlock_bh():

void spin_lock_bh(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
工作队列

总共有两种类型的工作:structure work_struct和struct delayed_work

struct delayed_work {
	struct work_struct work;
	struct timer_list timer;

	/* target workqueue and CPU ->timer uses to queue ->work */
	struct workqueue_struct *wq;
	int cpu;
};

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

有两种初始化工作条目的方式.一种是定义和初始化同事来进行,另一种是定义和初始化分开来做的.

#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 *));

DECLARE_WORK() and DECLARE_DELAYED_WORK() declare and initialize a work item, and INIT_WORK() and INIT_DELAYED_WORK() initialize an already declared work item.
在这里插入图片描述

定义并且初始化同时进行

#include <linux/workqueue.h>

void my_work_handler(struct work_struct *work);

DECLARE_WORK(my_work, my_work_handler);

定义和初始化分开进行

void my_work_handler(struct work_struct * work);

struct work_struct my_work;

INIT_WORK(&my_work, my_work_handler);

在前面已经进行了定义和初始化,那么我们如何来使用呢?

schedule_work(struct work_struct *work);

schedule_delayed_work(struct delayed_work *work, unsigned long delay);

schedule_delayed_work的delay时间是jiffies.

work items不能够被mask,但是可以调用cancel_delayed_work或者cancel_work_sync.

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

如果在调用cancel方法的时候work item已经正在执行,那么他将会继续执行.但是当调用了cancel方法之后,可以保证这个work item将不再再次运行.

注意:cancel_work()不是同步的,所以在执行清理工作的时候不要使用它,否则将会导致资源竞争.

We can wait for a workqueue to complete running all of its work items by calling flush_scheduled_work():

void flush_scheduled_work(void);

在这里插入图片描述
这个flush方法可能会导致阻塞,所以不要在中断上下文中使用.flush函数需要等待所有的work item运行完成,所以,cancel方法需要在flush方法之前调用.

Finally, the following functions can be used to schedule work items on a particular processor (schedule_delayed_work_on()), or on all processors (schedule_on_each_cpu()):
在这里插入图片描述

int schedule_delayed_work_on(int cpu, struct delayed_work *work, unsigned long delay);
int schedule_on_each_cpu(void(*function)(struct work_struct *));

A usual sequence to initialize and schedule a work item is the following:

void my_work_handler(struct work_struct *work);

struct work_struct my_work;

INIT_WORK(&my_work, my_work_handler);

schedule_work(&my_work);

等待work item执行的结束,可以使用

flush_scheduled_work();

正如你所看到的那样,my_work_handler函数接收task作为参数.为了访问module的私有数据,可以使用container_of

struct my_device_data {
    struct work_struct my_work;
    // ...
};

void my_work_handler(struct work_struct *work)
{
   structure my_device_data * my_data;

   my_data = container_of(work, struct my_device_data,  my_work);
   // ...
}

计划的工作将会在内核调用的线程上下文中执行中断处理程序.被称作是events/x. x是处理器号.内核会初始化内核线程

$ ps -e
PID TTY TIME CMD
1?  00:00:00 init
2 ?  00:00:00 ksoftirqd / 0
3 ?  00:00:00 events / 0 <--- kernel thread that runs work items
4 ?  00:00:00 khelper
5 ?  00:00:00 kthread
7?  00:00:00 kblockd / 0
8?  00:00:00 kacpid

上面的函数使用预先定义的工作队列,被称作是events.运行在线程上下文中.尽管已经足够了,但是这是共享的资源并且大的延迟,可以导致其他队列用户的延迟.所以下面是创建另外的queue的函数:

工作队列被struct workqueue_struct代表.新的工作队列可以使用下面的函数创建:

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

create_workqueue() uses one thread for each processor in the system, and create_singlethread_workqueue() uses a single thread.
在这里插入图片描述

在队列中添加task,使用queue_work或者queue_delayed_work方法

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);

等待所有的work item结束运行,可以使用flush_workqueue()

void flush_workqueue(struct worksqueue_struct * queue);

破坏所有的工作队列使用destroy_workqueue()

void destroy_workqueue(structure workqueque_struct *queue);

接着定义和初始化另外的工作队列,定义和初始化工作条目,并且将它添加到队列中

void my_work_handler(struct work_struct *work);

struct work_struct my_work;
struct workqueue_struct * my_workqueue;

my_workqueue = create_singlethread_workqueue("my_workqueue");
INIT_WORK(&my_work, my_work_handler);

queue_work(my_workqueue, &my_work);

接下来演示如何移除工作队列

flush_workqueue(my_workqueue);
destroy_workqueue(my_workqueue);

被绑定方法的work items将会运行在被称为my_workqueue的新的内核线程中.name被传进了create_singlethread_workqueue().

内核线程

内核线程的出现正是为了满足在进程的上下文中运行内核的代码。内核线程是工作队列机制的基石。从本质上来讲,内核线程是仅仅运行在内核模式的线程并且没有用户地址空间或者其他的属性。使用kthread_create()创建内核线程

#include <linux/kthread.h>

structure task_struct *kthread_create(int (*threadfn)(void *data),
                                      void *data, const char namefmt[], ...);

threadfn是运行在内核线程的方法
data是传入到这个函数的参数
namefmt 代表内核线程的名字,并且通过ps/top命令可以打印出来.可能包含%d ,%s序列.

kthread_create (f, NULL, "%skthread%d", "my", 0);

将会创建内核线程,名字是mythread0.
被创建的内核线程将会被stop(在TASK_INTERRUPTIBLE 状态).为了开始内核线程,可以调用wake_up_process()方法.

#include <linux/sched.h>
int wake_up_process(struct task_struct *p);

使用kthread_run()来创建并运行内核线程

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

尽管在内核线程中编程拥有很多限制,但是线程更加轻松并且scheduling更想试试用户空间的scheduling.可是,有些限制.我们可以将内核线程中可以或者不可以做的事情列举出来.

  • 不可以访问用户空间(甚至是copy_from_user和copy_to_user),因为内核线程没有用户地址空间.
  • 不能长时间进行忙等;如果内核以一种不可抢占的方式编译的,那么内核线程就不会被其他的内核线程抢占后者用户进程抢占,
  • 可以调用导致阻塞的操作
  • 可以使用spinlocks,但如果占用锁的时间很长,那么建议使用mutexes

内核线程的终止执行是自愿的,可以调用do_exit()

fastcall NORET_TYPE void do_exit(long code);

大多数内核线程的实现使用相同的模型,我们也建议这样做,这样可以避免共同的错误

#include <linux/kthread.h>

DECLARE_WAIT_QUEUE_HEAD(wq);

// list events to be processed by kernel thread
structure 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(&events->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);

内核线程的请求如下

void send_event(struct event *ev)
{
    spin_lock(&events_lock);
    list_add(&ev->lh, &list_events);
    spin_unlock(events_lock);
    wake_up(&wq);
}
# define jiffies	raid6_jiffies()

static inline uint32_t raid6_jiffies(void)
{
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return tv.tv_sec*1000 + tv.tv_usec/1000;
}

static void alias(void)
{
	struct aliasinfo *a;
	char *active = NULL;

	sort_aliases();
	link_slabs();

	for(a = aliasinfo; a < aliasinfo + aliases; a++) {

		if (!show_single_ref && a->slab->refs == 1)
			continue;

		if (!show_inverted) {
			if (active) {
				if (strcmp(a->slab->name, active) == 0) {
					printf(" %s", a->name);
					continue;
				}
			}
			printf("\n%-12s <- %s", a->slab->name, a->name);
			active = a->slab->name;
		}
		else
			printf("%-20s -> %s\n", a->name, a->slab->name);
	}
	if (active)
		printf("\n");
}

time_list的定义

struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct list_head entry;
	unsigned long expires;
	struct tvec_base *base;

	void (*function)(unsigned long);
	unsigned long data;

	int slack;

#ifdef CONFIG_TIMER_STATS
	int start_pid;
	void *start_site;
	char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

spin_lock_bh

static inline void spin_lock_bh(spinlock_t *lock)
{
	raw_spin_lock_bh(&lock->rlock);
}

创建一个简单的内核模块,展示信息在TIMER_TIMEOUT秒之后,在加载进内核之后.
更改之前的模块,使得在每个TIMER_TIMEOUT秒经过之后,输出信息.

/*
 * 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 size_t nseconds;

    nseconds += TIMER_TIMEOUT;
    pr_info("[timer_handler] nseconds = %d\n",nseconds);
	/* TODO 2: rechedule timer */
    mod_timer(tl,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);
oot@qemux86:~/skels/deferred_work/1-2-timer# insmod timer.ko 
[timer_init] Init module
root@qemux86:~/skels/deferred_work/1-2-timer# [timer_handler] nseconds = 1
[timer_handler] nseconds = 2
[timer_handler] nseconds = 3
[timer_handler] nseconds = 4
[timer_handler] nseconds = 5
[timer_handler] nseconds = 6

我们决定在从用户空间接收到ioctl调用之后的N秒展示当前进程的信息.N被当做是传入到ioctl中的参数.
MY_IOCTL_TIMER_SET来schedule一个timer.但是这个timer并不是周期运行的,这个命令接受一个值,不是指针.
MY_IOCTL_TIMER_CANCEL来使得timer失活.
注意:寻求一种方式来访问ioctl参数,在timer handler中打印出当前进程的Pid和进程执行镜像的名字.

为了在用户空间使用设备驱动,需要使用mknod工具创建设备字符文件 /dev/deferred.可以通过执行3-4-5-deferred/kernel/makenode脚本来完成这个操作.

激活或者取消激活timer,可以通过调用用户空间的ioctl操作来完成.使用3-4-45-deferred/user/test程序来测试并撤销timer.这个程序接受ioctl类型的操作并且它的参数在命令行当中.

为了在3秒后enable timer,可以使用./test s 3,disable时候,使用./test c

4 Blocking operations

接下来我们查看当执行block 操作的时候在timer routine中会发生什么.我们在timer-handling 中调用一个称为alloc_io的函数来模拟blocking operation

更改module以至于可以接收到MY_IOCTL_TIMER_ALLOC的时候,timer handler会调用alloc_io函数.

使用相同的timer,来在timer handler中区分功能,在device 结构中使用flag.使用TIMER_TYPE_ALLOC和TIMER_TYPE_SET宏来定义代码的结构.为了初始化,可以使用TIMER_TYPE_NONE.

导致error的驱动,是由于在atomic上下文中调用的阻塞的函数.(timer handler运行在中断上下文当中)

Workqueues

我们将更改模块来预防之前工作中的错误.为了这样做,可以使用workqueue来调用alloc_io,在work handler中使用timer hanlder来schedule一个work.

在struct work_struct中添加一个新的字段,初始化这个字段.使用schedule_work来使用timer handler来schedule一个work.在ioctl的N秒之后schedule.

/*
 * SO2 - Lab 6 - Deferred Work
 *
 * Exercises #3, #4, #5: deferred work
 *
 * Code skeleton.
 */
/*
 * 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 */
    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;

	task = pid_task(find_vpid(pid), PIDTYPE_PID);
	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:
#ifdef ALLOC_IO_DIRECT
            alloc_io();
#else
            schedule_work(&my_data->work);
#endif
            break;
        case TIMER_TYPE_MON:
        {
            struct mon_proc *p,*n;
            spin_lock(&my_data->lock);
            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;
        }
        default:
            break;
		/* TODO 3: schedule work */
		/* TODO 4: iterate the list and check the proccess state */
			/* 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 */
    }
}

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(&my_data->timer,jiffies + arg * HZ);
			break;
		case MY_IOCTL_TIMER_CANCEL:
			/* TODO 1: cancel timer */
            del_timer(&my_data->timer);
			break;
		case MY_IOCTL_TIMER_ALLOC:
			/* TODO 2: set flag and schedule timer */
            my_data->flag = TIMER_TYPE_ALLOC;
            mod_timer(&my_data->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(arg);
            if(IS_ERR(p))
                return PTR_ERR(p);
			/* 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 + 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){
        put_task_struct(p->task); 
        list_del(&p->list);
        kfree(p);
    }
		/* TODO 4: ... decrement task usage counter ... */
		/* TODO 4: ... remove it from the list ... */
		/* TODO 4: ... free the struct mon_proc */
}

module_init(deferred_init);
module_exit(deferred_exit);
```c
/*
 * 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/4: init the waitqueues and flags */
	init_waitqueue_head(&wq_stop_thread);
	atomic_set(&flag_stop_thread, 0);
	init_waitqueue_head(&wq_thread_terminated);
	atomic_set(&flag_thread_terminated, 0);
	/* TODO: create and start the kernel thread */
	kthread_run(my_thread_f, NULL, "%skthread%d", "my", 0);

	return 0;
}

static void __exit kthread_exit(void)
{
	/* TODO/2: 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);
kernel thread

实现一个简单的模型,创建内核线程,展示当前的进程id.

kthread_run()用于创建并运行线程

kthread_create用于首先创建一个挂起的线程,然后通过weke_up_process来运行

在timer和process之间共享Buffer

task的目的是在deferable(ltimer)和进程上下文之间保持同步,设置一个周期运行的tiemr来监视processes列表.如果有一个processes终止了,那么输出信息.processed可以被动态地添加进list.

当MY_IOCTL_TIMER_MON命令被接收,检查给定的process是否终止如果终止,那么添加进监视列表中.

使用get_proc检查pid,找到关联的struct task_struct并且分配一个struct mon_proc条目,可以添加到list中.这个方法增加对task的引用.所以当task终止的时候内存不会free.
注意:使用spinlock来保护对list的访问.自从和timer共享了数据,我们需要disablle handler的下半部,这是为了获取锁.
提示:从timer中美妙收集一个信息,使用timer,通过TIMER_TYPE_ACCT添加新的行为.

/*
 * 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);
    atomic_set(&flag_stop_thread,0);
    init_waitqueue_head(&wq_thread_terminated);
    atomic_set(&flag_thread_terminated,0);
	/* TODO: create and start the kernel thread */
    kthread_run(my_thread_f,NULL,"%skthread%d","my",0);

	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);
root@qemux86:~/skels/deferred_work/6-kthread# insmod kthread.ko 
[kthread_init] Init module
root@qemux86:~/skels/deferred_work/6-kthread# [my_thread_f] Current process id is 1673 (mykthread0)

root@qemux86:~/skels/deferred_work/6-kthread# lsmod 
    Tainted: G  
kthread 16384 0 - Live 0xc8853000 (O)
root@qemux86:~/skels/deferred_work/6-kthread# 
root@qemux86:~/skels/deferred_work/6-kthread# 
root@qemux86:~/skels/deferred_work/6-kthread# rmmod kthread
[my_thread_f] Exiting
[kthread_exit] Exit module
root@qemux86:~/skels/deferred_work/6-kthread# 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值