LDD3源码分析之时间与延迟操作

原创 2012年03月30日 21:12:22

作者:刘昊昱 

博客:http://blog.csdn.net/liuhaoyutz

编译环境:Ubuntu 10.10

内核版本:2.6.32-38-generic-pae

LDD3源码路径:examples/misc-modules/jit.c    examples/misc-modules/jiq.c

 

本文分析LDD37章的示例程序jit.cjiq.c,并给出了解决编译jiq.c文件时出现的错误的方法。

 

一、jit.c文件分析

jit.c程序是一个综合性的演示程序,涉及操作时间和延迟的各种技术。为了使程序代码最少,jit使用动态的/proc文件方式。

按照惯例,我们从模块初始化函数jit_init开始看:

263int __init jit_init(void)
264{
265    create_proc_read_entry("currentime", 0, NULL, jit_currentime, NULL);
266    create_proc_read_entry("jitbusy", 0, NULL, jit_fn, (void *)JIT_BUSY);
267    create_proc_read_entry("jitsched",0, NULL, jit_fn, (void *)JIT_SCHED);
268    create_proc_read_entry("jitqueue",0, NULL, jit_fn, (void *)JIT_QUEUE);
269    create_proc_read_entry("jitschedto", 0, NULL, jit_fn, (void *)JIT_SCHEDTO);
270
271    create_proc_read_entry("jitimer", 0, NULL, jit_timer, NULL);
272    create_proc_read_entry("jitasklet", 0, NULL, jit_tasklet, NULL);
273    create_proc_read_entry("jitasklethi", 0, NULL, jit_tasklet, (void *)1);
274
275    return 0; /* success */
276}

jit_init共调用了8create_proc_read_entry函数,我们在《LDD3源码分析之调试技术》一文中分析过这个函数,该函数用来创建一个只读的/proc入口项,其函数原型如下:
struct proc_dir_entry *create_proc_read_entry(const char *name, 
                                              mode_t mode,
                                              struct proc_dir_entry *base,
                                              read_proc_t *read_proc,
                                              void *data)

name是要创建的文件名称;

mode是该文件的保护掩码(传入0表示使用系统默认值)

base指定该文件所在的目录(如果baseNULL,则该文件将创建在/proc根目录下)

read_proc是实现该文件的读操作的函数;

data是传递给read_proc的参数。

这里我们重点看read_proc函数。为了创建一个只读的/proc文件,驱动程序必须实现一个函数,用于在读取文件时生成数据,这个函数称为read_proc。当某个进程读取这个/proc文件时(使用read系统调用),就会调用相应驱动程序的read_proc函数。

read_proc函数的原型如下:

int (*read_proc)(char *page, char **start, off_t off, int count, int *eof, void *data)

这个函数的参数比较难理解,这里我偷下懒,直接把文档Linux Kernel Procfs Guide上对这个函数的解释复制过来,大家自己看。另外,LDD3中文版,翻译上有许多不对的地方,建议大家看英文版的描述。

 

The read function is a call back function that allows userland processes to read data

from the kernel.

The read function should write its information into the page. For proper use, the function should start writing at an offset ofoffinpageand write at mostcountbytes, but because most read functions are quite simple and only return a small amount of information, these two parameters are usually ignored (it breaks pagers like more and less, but cat still works).

If the off and count parameters are properly used,eofshould be used to signal that the end of the file has been reached by writing 1 to the memory locationeofpoints to.

The parameter start doesn’t seem to be used anywhere in the kernel. Thedata

parameter can be used to create a single call back function for several files.

The read_func function must return the number of bytes written into thepage.

理解了create_proc_read_entryread_proc函数,我们就可以来看jit_init函数都完成了哪些工作了。8次调用create_proc_read_entry函数,也就是说,jit_init创建了8/proc入口项,文件名、read_proc函数以及传递给read_proc函数的参数的如下:

/proc/currentime      jit_currentime        NULL

/proc/jitbusy             jit_fn                      JIT_BUSY

/proc/jitsched           jit_fn                      JIT_SCHED

/proc/jitqueue          jit_fn                       JIT_QUEUE

/proc/jitschedto       jit_fn                        JIT_SCHEDTO

/proc/jitimer             jit_timer                   NULL

/proc/jitasklet          jit_tasklet                  NULL

/proc/jitasklethi       jit_tasklet                  1

创建了这些模块入口项之后,jit模块的初始化工作就完成了。下面我们按照LDD3讲的顺序,依次看每个/proc入口项完成什么工作。

首先来看/proc/currentime,它对应的read_proc函数是jit_currentime,其代码如下:

 91/*
 92 * This file, on the other hand, returns the current time forever
 93 */
 94int jit_currentime(char *buf, char **start, off_t offset,
 95                   int len, int *eof, void *data)
 96{
 97    struct timeval tv1;
 98    struct timespec tv2;
 99    unsigned long j1;
100    u64 j2;
101
102    /* get them four */
103    j1 = jiffies;
104    j2 = get_jiffies_64();
105    do_gettimeofday(&tv1);
106    tv2 = current_kernel_time();
107
108    /* print */
109    len=0;
110    len += sprintf(buf,"0x%08lx 0x%016Lx %10i.%06i\n"
111               "%40i.%09i\n",
112               j1, j2,
113               (int) tv1.tv_sec, (int) tv1.tv_usec,
114               (int) tv2.tv_sec, (int) tv2.tv_nsec);
115    *start = buf;
116    return len;
117}

jit_currentime函数以多种形式返回当前时间。

97 - 98行,定义了一个timeval类型的变量tv1和一个timespec类型的变量tv2,这两个结构体都定义在linux/time.h中:

struct timeval {
    time_t      tv_sec;     /* seconds */
    suseconds_t tv_usec;    /* microseconds */
};

struct timespec {
    time_t  tv_sec;     /* seconds */
    long    tv_nsec;    /* nanoseconds */
};

用户空间表述时间就是使用这两个结构体,timeval使用秒和毫秒,timespec使用秒和纳秒,timeval出现的更早,也更常用。

103行,将当前jiffies值保存在j1中,注意,j1unsigned long类型变量。

104行,调用get_jiffies_64函数,取得64位的jiffies值。

105行,调用do_gettimeofday函数,把当前jiffies值转换为timeval类型保存在tv1中。

106行,调用current_kernel_time函数取得当前jiffies值对应的timespec类型,保存在tv2中。

110 - 114行,将4种形式的时间值保存在buf指向的内存页中。

由上面的分析可见,当读/proc/currentime文件时,会打印4种形式的时间值。在我的机器上测试现象如下图所示:

接下来我们看jit_fn函数,/proc/jitbusy/proc/jitsched/proc/jitqueue/proc/jitschedto四个文件的read_proc函数都是jit_fn,所不同的是,读这四个文件时,会分别传递给jit_fn四个不同的参数JIT_BUSYJIT_SCHEDJIT_QUEUEJIT_SCHEDTO。函数代码如下:

 44/* use these as data pointers, to implement four files in one function */
 45enum jit_files {
 46    JIT_BUSY,
 47    JIT_SCHED,
 48    JIT_QUEUE,
 49    JIT_SCHEDTO
 50};
 51
 52/*
 53 * This function prints one line of data, after sleeping one second.
 54 * It can sleep in different ways, according to the data pointer
 55 */
 56int jit_fn(char *buf, char **start, off_t offset,
 57          int len, int *eof, void *data)
 58{
 59    unsigned long j0, j1; /* jiffies */
 60    wait_queue_head_t wait;
 61
 62    init_waitqueue_head (&wait);
 63    j0 = jiffies;
 64    j1 = j0 + delay;
 65
 66    switch((long)data) {
 67        case JIT_BUSY:
 68            while (time_before(jiffies, j1))
 69                cpu_relax();
 70            break;
 71        case JIT_SCHED:
 72            while (time_before(jiffies, j1)) {
 73                schedule();
 74            }
 75            break;
 76        case JIT_QUEUE:
 77            wait_event_interruptible_timeout(wait, 0, delay);
 78            break;
 79        case JIT_SCHEDTO:
 80            set_current_state(TASK_INTERRUPTIBLE);
 81            schedule_timeout (delay);
 82            break;
 83    }
 84    j1 = jiffies; /* actual value after we delayed */
 85
 86    len = sprintf(buf, "%9li %9li\n", j0, j1);
 87    *start = buf;
 88    return len;
 89}

44 - 50行,定义了JIT_BUSYJIT_SCHEDJIT_QUEUEJIT_SCHEDTO四个枚举值,分别对应0123

59行,定义了两个unsigned long类型变量j0, j1,用来保存jiffies值。

60 - 62行,定义并初始化了等待队列头wait

63行,将j0设置为当前jiffies值。

64行,将j1设置为j0 + delaydelay默认值在37行定义为HZ,也可以在命令行指定。

67 - 70行,如果传递给jit_fn的参数是JIT_BUSY,也就是说用户在读/proc/jitbusy文件,此时采用忙等待的方式延迟delay长度的时钟滴答。time_before(jiffies, j1)宏在jiffies小于j1的情况下为真。cpu_relax函数不做任何事情。

因为内核头文件中将jiffies声明为volatile型变量,所以每次C代码访问它时都会重新读取它,因此该循环可以直到延迟的作用。尽管也是正确的实现,但这个忙等待会严重降低系统性能。如果我们没有将内核配置为抢占型的,那么这个循环将在延迟期间独占处理器,在j1所代表的时间到达之前,系统看起来就像死掉了一样。而如果我们将内核配置为可抢占型的,则问题不会那么严重,这是因为除非代码拥有一个锁,否则处理器的时间还可以用作他用。但是在抢占式系统中,忙等待仍然有些浪费。

更糟糕的是,如果在进入while循环之前,禁止了中断,jiffies值就得不到更新,那么while循环的判定条件就永远为真了。

下图是测试/proc/jitbusy的过程:

上图中,第一次执行”dd bs=20 count=5 < /proc/jitbusy”,是在系统负载比较小的情况下。第二次执行”dd bs=20 count=5 < /proc/jitbusy”时,在另外一个终端下执行了LDD3提供的load50程序,该程序用来增加系统负载。

从上图中的输出内容可以看出,当系统负载较小时,每次读操作延迟恰好是1(1000jiffies),而下一次读操作会在前一个结束后立即执行。但是,当系统负载较大时,每个读操作的延迟恰好是1秒,但是下一次读操作可能会5秒后才会执行。

我们回到jit_fn函数继续往下看。

71 - 75行,如果传递给jit_fn的参数是JIT_SCHED,也就是说用户在读/proc/jitsched文件,此时如果time_before(jiffies, j1)宏为真,则调用schedule函数让出处理器。

虽然这种延迟技术在等待时放弃了CPU,但是性能仍然不理想。当前进程虽然放弃了CPU,但是它仍然在运行队列中,如果系统中只有一个可运行进程,则该进程又会立即运行,而空闲任务(进程号为0,称为swapper)从来不会运行。在系统空闲时运行swapper可以减轻处理器负载,延长处理器寿命,降低电量消耗。

下图是测试/proc/jitsched的过程:

上图中,第一次执行”dd bs=20 count=5 < /proc/jitsched”,是在系统负载比较小的情况下。第二次执行”dd bs=20 count=5 < /proc/jitsched”时,在另外一个终端下执行了load50程序增加系统负载。可以看出,在系统负载较大时,读操作的延迟比所请求的延迟时间长几个时钟滴答,系统越忙,这个问题越突出。当一个进程使用schedule释放处理器后,不能保证在进程需要时很快就能得到处理器。

因此,除了影响系统整体性能外,使用schedule延迟的方法对驱动程序需求来讲并不安全。

我们再回到jit_fn函数,76 - 78行,如果传递给jit_fn的参数是JIT_QUEUE,也就是说用户在读/proc/jitqueue文件,则调用wait_event_interruptible_timeout(wait, 0, delay)函数在等待队列wait上休眠,但是会在指定的等待时间到期时返回。在到期之前,如果进程收到信号也会退出休眠返回。

下图是测试/proc/jitqueue的过程:

从上图可以看出,即使是在高负载的系统上,对/proc/jitqueue的读取也将得到接近优化的结果。

回到jit_fn函数,现在看79 - 82行,如果传递给jit_fn的参数是JIT_SCHEDTO,也就是说用户在读/proc/schedto文件,则在80行调用set_current_state将进程状态设置为TASK_INTERRUPTIBLE,然后在81行调用schedule_timeout (delay)schedule_timeout函数让出处理器,并等待delay指定的时间。实际上wait_event_interruptible_timeout函数在内部就是利用了schedule_timeout函数。

使用schedule_timeout方式延迟,性能与使用wait_event_interruptible_timeout相似,但是可以避免声明和使用多余的等待队列。

下图是测试/proc/schedto文件的过程:

从上图打印的信息可以看出,即使是在高负载的系统上,对/proc/schedto的读取也将得到接近优化的结果。

到此,jit_fn函数我们就分析完了。下面我们来看jit_timer函数,它对应/proc/jitimer其代码如下:

155/* the /proc function: allocate everything to allow concurrency */
156int jit_timer(char *buf, char **start, off_t offset,
157          int len, int *eof, void *unused_data)
158{
159    struct jit_data *data;
160    char *buf2 = buf;
161    unsigned long j = jiffies;
162
163    data = kmalloc(sizeof(*data), GFP_KERNEL);
164    if (!data)
165        return -ENOMEM;
166
167    init_timer(&data->timer);
168    init_waitqueue_head (&data->wait);
169
170    /* write the first lines in the buffer */
171    buf2 += sprintf(buf2, "   time   delta  inirq    pid   cpu command\n");
172    buf2 += sprintf(buf2, "%9li  %3li     %i    %6i   %i   %s\n",
173            j, 0L, in_interrupt() ? 1 : 0,
174            current->pid, smp_processor_id(), current->comm);
175
176    /* fill the data for our timer function */
177    data->prevjiffies = j;
178    data->buf = buf2;
179    data->loops = JIT_ASYNC_LOOPS;
180
181    /* register the timer */
182    data->timer.data = (unsigned long)data;
183    data->timer.function = jit_timer_fn;
184    data->timer.expires = j + tdelay; /* parameter */
185    add_timer(&data->timer);
186
187    /* wait for the buffer to fill */
188    wait_event_interruptible(data->wait, !data->loops);
189    if (signal_pending(current))
190        return -ERESTARTSYS;
191    buf2 = data->buf;
192    kfree(data);
193    *eof = 1;
194    return buf2 - buf;
195}

159行,定义了jit_data结构体指针变量datajit_data结构体定义如下:

126/* This data structure used as "data" for the timer and tasklet functions */
127struct jit_data {
128    struct timer_list timer;
129    struct tasklet_struct tlet;
130    int hi; /* tasklet or tasklet_hi */
131    wait_queue_head_t wait;
132    unsigned long prevjiffies;
133    unsigned char *buf;
134    int loops;
135};

167行,调用init_timer初始化定时器data->timer

168行,调用init_waitqueue_head初始化等待队列头data->wait

171行,向buf2中写入标题头,同时更新buf2的值。

172行,向buf2中写入相关数据,同时更新buf2的值。

177 - 179行,初始化data->prevjiffies为当前jiffies值,data->buf指向buf2data->loops设置为 JIT_ASYNC_LOOPSJIT_ASYNC_LOOPS136行定义为5

181 - 185行,设置和注册定时器dev->timer。定时器函数为jit_timer_fn,传递给该函数的参数为data变量,定时器等待时间为j+tdelay,最后调用add_timer将定时器注册到系统中,此时,即启动了定时器。

188行,在data->wait上休眠等待,注意唤醒条件是data->loops变为0

193行,将*eof设置为1,表示读/proc/jitimer文件结束。

下面该分析定时器函数jit_timer_fn了,其代码如下:

138void jit_timer_fn(unsigned long arg)
139{
140    struct jit_data *data = (struct jit_data *)arg;
141    unsigned long j = jiffies;
142    data->buf += sprintf(data->buf, "%9li  %3li     %i    %6i   %i   %s\n",
143                 j, j - data->prevjiffies, in_interrupt() ? 1 : 0,
144                 current->pid, smp_processor_id(), current->comm);
145
146    if (--data->loops) {
147        data->timer.expires += tdelay;
148        data->prevjiffies = j;
149        add_timer(&data->timer);
150    } else {
151        wake_up_interruptible(&data->wait);
152    }
153}

在定时器函数jit_timer_fn中,首先向data->buf中写入相关数据,然后判断data->loops的值是否为0,如果不为0,重新设置相关参数并再次启动定时器;如果data->loops的值变为0,则唤醒等待队列data->wait上的进程。

测试/proc/jitimer的过程如下图所示:

下面我们来看jit_tasklet函数,该函数对应的文件是/proc/ jitasklet/proc/ jitasklethi,代码如下:

216/* the /proc function: allocate everything to allow concurrency */
217int jit_tasklet(char *buf, char **start, off_t offset,
218          int len, int *eof, void *arg)
219{
220    struct jit_data *data;
221    char *buf2 = buf;
222    unsigned long j = jiffies;
223    long hi = (long)arg;
224
225    data = kmalloc(sizeof(*data), GFP_KERNEL);
226    if (!data)
227        return -ENOMEM;
228
229    init_waitqueue_head (&data->wait);
230
231    /* write the first lines in the buffer */
232    buf2 += sprintf(buf2, "   time   delta  inirq    pid   cpu command\n");
233    buf2 += sprintf(buf2, "%9li  %3li     %i    %6i   %i   %s\n",
234            j, 0L, in_interrupt() ? 1 : 0,
235            current->pid, smp_processor_id(), current->comm);
236
237    /* fill the data for our tasklet function */
238    data->prevjiffies = j;
239    data->buf = buf2;
240    data->loops = JIT_ASYNC_LOOPS;
241
242    /* register the tasklet */
243    tasklet_init(&data->tlet, jit_tasklet_fn, (unsigned long)data);
244    data->hi = hi;
245    if (hi)
246        tasklet_hi_schedule(&data->tlet);
247    else
248        tasklet_schedule(&data->tlet);
249
250    /* wait for the buffer to fill */
251    wait_event_interruptible(data->wait, !data->loops);
252
253    if (signal_pending(current))
254        return -ERESTARTSYS;
255    buf2 = data->buf;
256    kfree(data);
257    *eof = 1;
258    return buf2 - buf;
259}

jit_tasklet的实现与前面分析的jit_timer很类似,相同的地方这里我们不仔细分析了,这里重点看一下两个函数不同的地方。

243行,调用tasklet_init初始化data->tlet,指定tasklet函数为jit_tasklet_fn,交将data作为参数传递给jit_tasklet_fn

245 - 246行,如果hi1,则使用tasklet_hi_schedule函数以高优先级调度data->tlet执行。当软件中断处理程序运行时,它会在处理其他软件中断(包括普通的tasklet)之前,处理高优先级的tasklet

247 - 248行,如果hi0,则使用tasklet_schedule调度data->tlet执行。

251行,在data->wait上休眠等待,注意唤醒条件是data->loops变为0

257行,将*eof设置为1,表示读/proc/ jitasklet/proc/ jitasklethi文件结束。

下面看tasklet函数jit_tasklet_fn

197void jit_tasklet_fn(unsigned long arg)
198{
199    struct jit_data *data = (struct jit_data *)arg;
200    unsigned long j = jiffies;
201    data->buf += sprintf(data->buf, "%9li  %3li     %i    %6i   %i   %s\n",
202                 j, j - data->prevjiffies, in_interrupt() ? 1 : 0,
203                 current->pid, smp_processor_id(), current->comm);
204
205    if (--data->loops) {
206        data->prevjiffies = j;
207        if (data->hi)
208            tasklet_hi_schedule(&data->tlet);
209        else
210            tasklet_schedule(&data->tlet);
211    } else {
212        wake_up_interruptible(&data->wait);
213    }
214}

该函数的功能与前面介绍的定时器函数jit_timer_fn非常像,只不过把调度定时器的操作换成了调度tasklet的函数。

下图是测试/proc/ jitasklet/proc/ jitasklethi的过程:

至此,jit.c文件我们就全部分析完了。

 

二、jiq.c文件分析

LDD37章涉及的代码还有一个jiq.c文件,这个文件主要演示了等待队列的用法。下面是jiq模块初始化函数jiq_init的代码:

240static int jiq_init(void)
241{
242
243    /* this line is in jiq_init() */
244    INIT_WORK(&jiq_work, jiq_print_wq, &jiq_data);
245
246    create_proc_read_entry("jiqwq", 0, NULL, jiq_read_wq, NULL);
247    create_proc_read_entry("jiqwqdelay", 0, NULL, jiq_read_wq_delayed, NULL);
248    create_proc_read_entry("jitimer", 0, NULL, jiq_read_run_timer, NULL);
249    create_proc_read_entry("jiqtasklet", 0, NULL, jiq_read_tasklet, NULL);
250
251    return 0; /* succeed */
252}

244行,初始化一个工作jiq_work,其工作函数是jiq_print_wqjiq_data是传递给该函数的参数。jiq_workwork_struct类型的变量,struct work_structlinux/workqueue.h文件中定义如下:

struct work_struct {
    atomic_long_t data;
#define WORK_STRUCT_PENDING 0       /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
    struct list_head entry;
    work_func_t func;
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

jiq_data是一个全局变量,其定义如下:

63static struct clientdata {
 64    int len;
 65    char *buf;
 66    unsigned long jiffies;
 67    long delay;
 68} jiq_data;

246 - 249行,创建4/proc只读接口,它们分别是/proc/ jiqwq/proc/ jiqwqdelay/proc/ jitimer/proc/ jiqtasklet,这4个文件对应的read_proc函数分别是jiq_read_wqjiq_read_wq_delayedjiq_read_run_timerjiq_read_tasklet

首先来看jiq_read_wq函数的实现:

129static int jiq_read_wq(char *buf, char **start, off_t offset,
130                   int len, int *eof, void *data)
131{
132    DEFINE_WAIT(wait);
133
134    jiq_data.len = 0;                /* nothing printed, yet */
135    jiq_data.buf = buf;              /* print in this place */
136    jiq_data.jiffies = jiffies;      /* initial time */
137    jiq_data.delay = 0;
138
139    prepare_to_wait(&jiq_wait, &wait, TASK_INTERRUPTIBLE);
140    schedule_work(&jiq_work);
141    schedule();
142    finish_wait(&jiq_wait, &wait);
143
144    *eof = 1;
145    return jiq_data.len;
146}

jiq_read_wq函数中使用的是共享工作队列,而不是进程专用的工作队列,共享工作队列更节约系统资源。该函数的休眠采用的是手工休眠。

132行,使用DEFINE_WAIT宏定义一个等待队列入口wait

134 - 137行,对jiq_data进行初始化。

139行,调用prepare_to_wait函数将wait加入到等待队列jiq_wait中,并把进程状态设置为TASK_INTERRUPTIBLE

140行,调用schedule_workjiq_work提交到系统提供的共享工作队列中。

141行,调用schedule函数,让出处理器,此后,进程就在等待队列jiq_wait中休眠。

142行,休眠被唤醒后,调用finish_wait做一些清理工作。

144行,将*eof设置为1,表明已经读到/proc/jiqwq的结束处。

把工作jiq_work提交给共享工作队列后,在此后的某一时间,工作函数jiq_print_wq就会执行,该函数代码如下:

111/*
112 * Call jiq_print from a work queue
113 */
114static void jiq_print_wq(void *ptr)
115{
116    struct clientdata *data = (struct clientdata *) ptr;
117
118    if (! jiq_print (ptr))
119        return;
120
121    if (data->delay)
122        schedule_delayed_work(&jiq_work, data->delay);
123    else
124        schedule_work(&jiq_work);
125}

工作函数jiq_print_wq的核心代码在jiq_print函数中,如果该函数返回0,则退出。如果返回非0值,说明还需要再次调用该工作。如果jiq_print函数返回非0值,根据data->delay的值是否为0,分别采用schedule_delayed_workschedule_work,以延迟和非延迟的方式重新调度工作。

jiq_print函数定义如下:

78/*
 79 * Do the printing; return non-zero if the task should be rescheduled.
 80 */
 81static int jiq_print(void *ptr)
 82{
 83    struct clientdata *data = ptr;
 84    int len = data->len;
 85    char *buf = data->buf;
 86    unsigned long j = jiffies;
 87
 88    if (len > LIMIT) {
 89        wake_up_interruptible(&jiq_wait);
 90        return 0;
 91    }
 92
 93    if (len == 0)
 94        len = sprintf(buf,"    time  delta preempt   pid cpu command\n");
 95    else
 96        len =0;
 97
 98    /* intr_count is only exported since 1.3.5, but 1.99.4 is needed anyways */
 99    len += sprintf(buf+len, "%9li  %4li     %3i %5i %3i %s\n",
100            j, j - data->jiffies,
101            preempt_count(), current->pid, smp_processor_id(),
102            current->comm);
103
104    data->len += len;
105    data->buf += len;
106    data->jiffies = j;
107    return 1;
108}

88 - 91行,如果打印的信息已经超过了LIMIT()的限制,则唤醒等待队列jiq_wait上的进程,并返回0,表示不需要再次调度该工作。

93 - 102行,打印相关信息。

104 - 107行,更新jiq_data的信息。

108行,返回1,表明还需要再次调度该工作。

下图是对/proc/jiqwq的测试过程:

下面我们来看jiq_read_wq_delayed函数,其代码如下:

149static int jiq_read_wq_delayed(char *buf, char **start, off_t offset,
150                   int len, int *eof, void *data)
151{
152    DEFINE_WAIT(wait);
153
154    jiq_data.len = 0;                /* nothing printed, yet */
155    jiq_data.buf = buf;              /* print in this place */
156    jiq_data.jiffies = jiffies;      /* initial time */
157    jiq_data.delay = delay;
158
159    prepare_to_wait(&jiq_wait, &wait, TASK_INTERRUPTIBLE);
160    schedule_delayed_work(&jiq_work, delay);
161    schedule();
162    finish_wait(&jiq_wait, &wait);
163
164    *eof = 1;
165    return jiq_data.len;
166}

这个函数与前面分析的jiq_read_wq非常相似,区别是这里采用延迟方式调度工作。这里我只看两个函数不同的地方,如果有不明白的,参考jiq_read_wq理解。

157行,因为要采用延迟方式,设置jiq_data.delay的值为delay,其默认值为1,可以通过命令行传递参数修改。

160行,schedule_delayed_work以延迟模式将工作提交到工作队列。

把工作jiq_work提交给共享工作队列后,在此后的某一时间,工作函数jiq_print_wq就会执行,该函数我们前面已经分析过,/proc/jiqwqdelay/proc/jiqwq的区别是因为指定了jiq_data.delay的值为非0值,所以121行,如果需要再次调度工作,还是用延迟模式调度。

下图是测试/proc/jiqwqdelay的过程:

下面我们来看jiq_read_run_timer函数,这个函数使用的是定时器,其代码如下:

212static int jiq_read_run_timer(char *buf, char **start, off_t offset,
213                   int len, int *eof, void *data)
214{
215
216    jiq_data.len = 0;           /* prepare the argument for jiq_print() */
217    jiq_data.buf = buf;
218    jiq_data.jiffies = jiffies;
219
220    init_timer(&jiq_timer);              /* init the timer structure */
221    jiq_timer.function = jiq_timedout;
222    jiq_timer.data = (unsigned long)&jiq_data;
223    jiq_timer.expires = jiffies + HZ; /* one second */
224
225    jiq_print(&jiq_data);   /* print and go to sleep */
226    add_timer(&jiq_timer);
227    interruptible_sleep_on(&jiq_wait);  /* RACE */
228    del_timer_sync(&jiq_timer);  /* in case a signal woke us up */
229
230    *eof = 1;
231    return jiq_data.len;
232}

有了前面分析的基础,我们看这个函数应该比较轻松,因为大部分代码和前面分析过的代码是一样的。

216 - 218行,初始化jiq_data

220 - 223行,初始化定时器jiq_timer,定时器函数是jiq_timedout

225行,调用jiq_print打印标题栏和一行信息。

226行,启动定时器。

227行,进程在jiq_wait上休眠等待。

定时器函数是jiq_timedout的代码如下:

205static void jiq_timedout(unsigned long ptr)
206{
207    jiq_print((void *)ptr);            /* print a line */
208    wake_up_interruptible(&jiq_wait);  /* awake the process */
209}

该定时器函数只执行一次,207行打印一行信息,208行唤醒等待进程。

下图是测试/proc/jitimer的过程:

下面我们看jiq_read_tasklet函数,这个函数使用的是tasklet,其代码如下:

182static int jiq_read_tasklet(char *buf, char **start, off_t offset, int len,
183                int *eof, void *data)
184{
185    jiq_data.len = 0;                /* nothing printed, yet */
186    jiq_data.buf = buf;              /* print in this place */
187    jiq_data.jiffies = jiffies;      /* initial time */
188
189    tasklet_schedule(&jiq_tasklet);
190    interruptible_sleep_on(&jiq_wait);    /* sleep till completion */
191
192    *eof = 1;
193    return jiq_data.len;
194}

189行,调度jiq_tasklet

190行,进程在jiq_wait上休眠。

jiq_tasklet在第75行定义:

75static DECLARE_TASKLET(jiq_tasklet, jiq_print_tasklet, (unsigned long)&jiq_data);

tasklet函数是jiq_print_tasklet,其定义如下:

171/*
172 * Call jiq_print from a tasklet
173 */
174static void jiq_print_tasklet(unsigned long ptr)
175{
176    if (jiq_print ((void *) ptr))
177        tasklet_schedule (&jiq_tasklet);
178}

调用jiq_print打印信息,如果jiq_print返回非0值,重新调度task_let

测试/proc/jiqtasklet的过程如下图所示:

 

三、编译jiq模块时出现的问题

在编译jiq模块过程中,出现如下图所示错误:

这是因为linux/config.h2.6.32-38-generic-pae内核中不存在了,解决方法是直接把jiq的第18行删除即可。再次编译,出现如下错误:

这是因为在2.6.32-38-generic-pae内核中,INIT_WORK宏的定义发生了变化,只能接受两个参数,但是LDD3传递了3个参数。解决办法是把243

243    INIT_WORK(&jiq_work, jiq_print_wq, &jiq_data);

替换为

243    INIT_WORK(&jiq_work, jiq_print_wq);

这样替换完成后,还有几个连锁反应需要处理。因为LDD3中的本意是把jiq_data作为参数传递给jiq_print_wq函数。现在新的INIT_WORK中无法传递参数了,所以我们需要修改jiq_print_wq函数,手动把jiq_data传递过去。修改后的jiq_print_wq代码如下:

113static void jiq_print_wq(struct work_struct *work)
114{
115    struct clientdata *data = (struct clientdata *) &jiq_data;
116
117    if (! jiq_print (&jiq_data))
118        return;
119
120    if (data->delay)
121        schedule_delayed_work(&jiq_work, data->delay);
122    else
123        schedule_work(&jiq_work);
124}

与原来相比,共修改了3个地方。

113行,参数类型改为work_struck结构指针,这是新内核定义的参数类型。

115行,直接使用全局变量jiq_data,原来是通过参数传递的。

117行,直接使用全局变量jiq_data,原来是通过参数传递的。

修改后,再编译,能编译通过,但仍有问题,如下图所示:

这是因为,在2.6.32-38-generic-pae内核中,schedule_delayed_work函数的定义发生了变化,现在其函数原型是

int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)

而在LDD3使用的2.6.10版本的内核中,其函数原型是:

int fastcall schedule_delayed_work(struct work_struct *work, unsigned long delay)

所以要在新内核上执行schedule_delayed_work,原来的work_struct必须改为delayed_work

delayed_work结构体定义如下:

struct delayed_work {
    struct work_struct work;
    struct timer_list timer;
};

为了使修改的代码最少,我做了如下修改:

1、在第56行定义了一个全局delayed_work结构体变量 jiq_delayed_work;

56static struct delayed_work jiq_delayed_work;

2、重新定义jiq_read_wq_delayed函数如下:

148static int jiq_read_wq_delayed(char *buf, char **start, off_t offset,
149                   int len, int *eof, void *data)
150{
151    DEFINE_WAIT(wait);
152    INIT_DELAYED_WORK(&jiq_delayed_work, jiq_print_wq);
153
154    jiq_data.len = 0;                /* nothing printed, yet */
155    jiq_data.buf = buf;              /* print in this place */
156    jiq_data.jiffies = jiffies;      /* initial time */
157    jiq_data.delay = delay;
158
159    prepare_to_wait(&jiq_wait, &wait, TASK_INTERRUPTIBLE);
160    schedule_delayed_work(&jiq_delayed_work, delay);
161    schedule();
162    finish_wait(&jiq_wait, &wait);
163
164    *eof = 1;
165    return jiq_data.len;
166}

与原来的相比,增加了152行初始化jiq_delayed_work。修改了160行,调度jiq_delayed_work

3、修改工作函数jiq_print_wq

113static void jiq_print_wq(struct work_struct *work)
114{
115    struct clientdata *data = (struct clientdata *) &jiq_data;
116
117    if (! jiq_print (&jiq_data))
118        return;
119
120    if (data->delay)
121        schedule_delayed_work(&jiq_delayed_work, data->delay);
122    else
123        schedule_work(&jiq_work);
124}

与原来相比,修改了121行,调度jiq_delayed_work

修改完成后,编译通过,如下图所示:

【Linux 驱动】第七章 时间 延迟及延缓操作 (例子详解一)

本文所涉及实验为博文http://blog.csdn.net/tianshuai11/article/details/7465587中示例,请先阅读上述博文,然后消化以下例子 一,模块方法  ...

linux驱动中的互斥途径三:自旋锁

介绍互斥机制中的互斥锁

IO 的阻塞和非阻塞二:轮询操作

介绍驱动中 IO 的非阻塞的 poll/select

LDD3源码分析之时间与延迟操作

转载自http://blog.csdn.net/liuhaoyutz/article/details/7412931 作者:刘昊昱  博客:http://blog.csdn.net/liu...

LDD3源码分析之时间与延迟操作

本博客转载于:http://blog.csdn.net/liuhaoyutz/article/details/7412931 [作者:刘昊昱] 本文分析LDD3第7章的示例程序jit.c...

LDD3源码分析之ioctl操作

作者:刘昊昱  博客:http://blog.csdn.net/liuhaoyutz 编译环境:Ubuntu 10.10 内核版本:2.6.32-38-generic-pae LDD3源码路径:e...

LDD3源码分析之ioctl操作

作者:刘昊昱  博客:http://blog.csdn.net/liuhaoyutz 编译环境:Ubuntu 10.10 内核版本:2.6.32-38-generic-pae LDD3源...

LDD3读书笔记(第8章 时间、延迟及延缓操作)

计时 #include HZ     HZ符号指出每秒钟产生的时钟滴答数。 #include volatile unsigned long jiffies u64 jiffies_64...

LDD3源码分析之访问控制

作者:刘昊昱  博客:http://blog.csdn.net/liuhaoyutz 编译环境:Ubuntu 10.10 内核版本:2.6.32-38-generic-pae LDD3源...

LDD3源码分析之slab高速缓存

作者:刘昊昱  博客:http://blog.csdn.net/liuhaoyutz 编译环境:Ubuntu 10.10 内核版本:2.6.32-38-generic-pae LDD3源...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:LDD3源码分析之时间与延迟操作
举报原因:
原因补充:

(最多只允许输入30个字)