学习ldd3--工作队列(第七章)

来源:互联网 发布:乐秀rx5数据 编辑:程序博客网 时间:2024/06/02 17:09
作者:张伟AreS
/******************************************************************************/
学习此之前先需学习《学习ldd3--proc文件系统(第七章与第四章)》
时钟中断、内核定时器、Tasklet代码均在jit.c中
工作队列    (代码在D:\学习\个人学习笔记及网络经典文章\学习LDD3笔记\ldd3_examples\misc-modules\jiq.c中)
 
  工作队列workqueue类似于tasket,但tasket的代码必须是原子的,而workqueue可以休眠
  工作队列类似 taskets,允许内核代码请求在将来某个时间调用一个函数,不同在于:
  (1)tasklet 在软件中断上下文中运行,所以 tasklet 代码必须是原子的。而工作队列函数在一个特殊内核进程上下文运行,有更多的灵活性,且能够休眠。
  (2)tasklet 只能在最初被提交的处理器上运行,这只是工作队列默认工作方式。
  (3)内核代码可以请求工作队列函数被延后一个给定的时间间隔。
  (4)tasklet 执行的很快, 短时期, 并且在原子态, 而工作队列函数可能是长周期且不需要是原子的,两个机制有它适合的情形。
工作队列有 struct workqueue_struct 类型,在 <linux/workqueue.h> 中定义。一个工作队列必须明确的在使用前创建,宏为:
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);
 每个工作队列有一个或多个专用的进程("内核线程"), 这些进程运行提交给这个队列的函数。
 若使用 create_workqueue, 就得到一个工作队列它在系统的每个处理器上有一个专用的线程。
 在很多情况下,过多线程对系统性能有影响,如果单个线程就足够则使用 create_singlethread_workqueue 来创建工作队列。
提交一个任务给一个工作队列,需要填充work_struct或delayed_work结构
/*若在运行时需要建立 work_struct 或 delayed_work结构, 使用下面 2 个宏定义:*/
INIT_WORK(struct work_struct *work, void (*function)(void *));
PREPARE_WORK(struct work_struct *work, void (*function)(void *));
INIT_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));
PREPARE_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));
/*有 2 个函数来提交工作给一个工作队列:*/
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到给定的workqueue。如果使用 queue_delay_work, 则实际的工作至少要经过指定的 jiffies 才会被执行。
 * 这些函数若返回 1 则工作被成功加入到队列; 若为0,则意味着这个 work 已经在队列中等待,不能再次加入
 */
 
创建4个/proc只读接口,它们分别是/proc/ jiqwq、/proc/ jiqwqdelay、/proc/ jitimer、/proc/ jiqtasklet,
这4个文件对应的read_proc函数分别是jiq_read_wq、jiq_read_wq_delayed、jiq_read_run_timer、jiq_read_tasklet。
/*
 * the init/clean material
 */
static int jiq_init(void)
{
 /* this line is in jiq_init() */
 INIT_WORK(&jiq_work, jiq_print_wq, &jiq_data);//初始化一个工作jiq_work,其工作函数是jiq_print_wq,jiq_data是传递给该函数的参数。
                                               //jiq_work是work_struct类型的变量
 create_proc_read_entry("jiqwq", 0, NULL, jiq_read_wq, NULL);
 create_proc_read_entry("jiqwqdelay", 0, NULL, jiq_read_wq_delayed, NULL);
 create_proc_read_entry("jitimer", 0, NULL, jiq_read_run_timer, NULL);
 create_proc_read_entry("jiqtasklet", 0, NULL, jiq_read_tasklet, NULL);
 return 0; /* succeed */
}
 
首先来看jiq_read_wq函数的实现:
定义全局变量 jiq_data
static struct clientdata { 
  int len; 
  char *buf;   
  unsigned long jiffies; 
  long delay; 
} jiq_data;
jiq_read_wq函数中使用的是共享工作队列,而不是进程专用的工作队列,共享工作队列更节约系统资源。该函数的休眠采用的是手工休眠。
所以没有queue_work函数
static int jiq_read_wq(char *buf, char **start, off_t offset,
                   int len, int *eof, void *data)
{
 DEFINE_WAIT(wait);//使用DEFINE_WAIT宏定义一个等待队列入口wait
 /*对jiq_data进行初始化*/
 jiq_data.len = 0;                /* nothing printed, yet */
 jiq_data.buf = buf;              /* print in this place */
 jiq_data.jiffies = jiffies;      /* initial time */
 jiq_data.delay = 0;
   
 prepare_to_wait(&jiq_wait, &wait, TASK_INTERRUPTIBLE);//将wait加入到等待队列jiq_wait中,并把进程状态设置为TASK_INTERRUPTIBLE
 schedule_work(&jiq_work);//将jiq_work提交到系统提供的共享工作队列中
 schedule();调用schedule函数,让出处理器,此后,进程就在等待队列jiq_wait中休眠
 finish_wait(&jiq_wait, &wait);//休眠被唤醒后,调用finish_wait做一些清理工作
 *eof = 1;//将*eof设置为1,表明已经读到/proc/jiqwq的结束处
 return jiq_data.len;
}
上文中:INIT_WORK(&jiq_work, jiq_print_wq, &jiq_data)
jiq_print_wq函数:
 
  工作函数jiq_print_wq的核心代码在jiq_print函数中,如果该函数返回0,则退出。
  如果返回非0值,说明还需要再次调用该工作。如果jiq_print函数返回非0值,
  根据data->delay的值是否为0,分别采用schedule_delayed_work和schedule_work,以延迟和非延迟的方式重新调度工作。

/*
 * Call jiq_print from a work queue
 */
static void jiq_print_wq(void *ptr)
{
 struct clientdata *data = (struct clientdata *) ptr;
   
 if (! jiq_print (ptr))
  return;
   
 if (data->delay)
  schedule_delayed_work(&jiq_work, data->delay);
 else
  schedule_work(&jiq_work);
}
jiq_print函数代码:
/*
 * Do the printing; return non-zero if the task should be rescheduled.
 */
static int jiq_print(void *ptr)
{
 struct clientdata *data = ptr;
 int len = data->len;
 char *buf = data->buf;
 unsigned long j = jiffies;
 if (len > LIMIT) { //如果打印的信息已经超过了LIMIT()的限制,则唤醒等待队列jiq_wait上的进程,并返回0,表示不需要再次调度该工作
  wake_up_interruptible(&jiq_wait);
  return 0;
 }
 if (len == 0)
  len = sprintf(buf,"    time  delta preempt   pid cpu command\n");
 else
  len =0;
   /* intr_count is only exported since 1.3.5, but 1.99.4 is needed anyways */
 len += sprintf(buf+len, "%9li  %4li     %3i %5i %3i %s\n",
   j, j - data->jiffies,
   preempt_count(), current->pid, smp_processor_id(),
   current->comm);
  
   /*更新jiq_data的信息*/
 data->len += len;
 data->buf += len;
 data->jiffies = j;
 return 1;//返回1,表明还需要再次调度该工作
}
原创粉丝点击