作者:张伟AreS
/******************************************************************************/
学习此之前先需学习《学习ldd3--proc文件系统(第七章与第四章)》
实例:代码:ldd3的jit.c (D:\学习\个人学习笔记及网络经典文章\学习LDD3笔记\ldd3_examples\misc-modules\jit.c)
时钟中断:
由系统的定时硬件以周期性的时间间隔产生,这个间隔(即频率)由内核根据HZ来确定,
HZ是一个与体系结构无关的常数,可配置(50-1200),在X86平台,默认是1000(1sec 1000次)
每当时钟中断发生时,全局变量jiffies(unsigned long)就会加1.因此jiffies记录了自linux启动后时钟发生中断此时。
驱动程序常利用jiffies来计算不同事件间的时间间隔
源码:jit.c jiq.c (D:\学习\个人学习笔记及网络经典文章\学习LDD3笔记\ldd3_examples\misc-modules中)
查看/proc/currentime,它对应的read_proc函数是jit_currentime
int jit_currentime(char *buf, char **start, off_t offset,int len, int *eof, void *data)
{
struct timeval tv1;//这两个结构体都定义在linux/time.h中
struct timespec tv2;
unsigned long j1;
u64 j2;
/* get them four */
j1 = jiffies;
j2 = get_jiffies_64();//获得64位的jiffies的值
do_gettimeofday(&tv1);//调用do_gettimeofday函数,把当前jiffies值转换为timeval类型保存在tv1中
tv2 = current_kernel_time();//调用current_kernel_time函数取得当前jiffies值对应的timespec(秒和纳秒)类型,保存在tv2中
/* 将4种形式的时间值保存在buf指向的内存页中*/
len=0;
/*sprintf函数将数值保存在buf中*/
len += sprintf(buf,"0x%08lx 0x%016Lx %10i.%06i\n" "%40i.%09i\n",j1, j2,(int) tv1.tv_sec, (int) tv1.tv_usec,(int) tv2.tv_sec, (int) tv2.tv_nsec);
*start = buf;
return len;
}
/*内核中struct timval与struct timespec结构体的定义*/
struct timeval {
time_t tv_sec; /* seconds 秒*/
suseconds_t tv_usec; /* microseconds毫秒 */
};
struct timespec {
time_t tv_sec; /* seconds 秒*/
long tv_nsec; /* nanoseconds纳秒 */
};
/***************************************/
延迟操作
接下来我们看jit_fn函数,/proc/jitbusy、/proc/jitsched、/proc/jitqueue、/proc/jitschedto
四个文件的read_proc函数都是jit_fn,所不同的是,读这四个文件时,会分别传递给jit_fn四个不同的
参数JIT_BUSY、JIT_SCHED、JIT_QUEUE、JIT_SCHEDTO
/* use these as data pointers, to implement four files in one function */
/*定义了JIT_BUSY、JIT_SCHED、JIT_QUEUE、JIT_SCHEDTO四个枚举值,分别对应0,1,2,3*/
enum jit_files {
JIT_BUSY,
JIT_SCHED,
JIT_QUEUE,
JIT_SCHEDTO
};
/*
* This function prints one line of data, after sleeping one second.
* It can sleep in different ways, according to the data pointer
*/
int jit_fn(char *buf, char **start, off_t offset,int len, int *eof, void *data)
{
unsigned long j0, j1; /* jiffies */
/*定义并初始化了等待队列头wait*/
wait_queue_head_t wait;
init_waitqueue_head (&wait);
j0 = jiffies; /*将j0设置为当前jiffies值*/
j1 = j0 + delay; /*delay默认值定义为HZ,HZ为1000,j1即未来1秒*/
/*长延迟执行*/
switch((long)data) {
/*1.忙等待。如果传递给jit_fn的参数是JIT_BUSY,也就是说用户在读/proc/jitbusy文件,
*此时采用忙等待的方式延迟delay长度的时钟滴答。time_before(jiffies, j1)宏在jiffies小于j1的情况下为真。
*cpu_relax函数不做任何事情。
*/
case JIT_BUSY:
while (time_before(jiffies, j1)) //time_before()宏表示在jiffies cpu_relax();//如果我们没有将内核配置为抢占型的,那么这个循环将在延迟期间独占处理器,
//在j1所代表的时间到达之前,系统看起来就像死掉了一样。而如果我们将内核配置为可抢占型的,则问题不会那么严重,
//这是因为除非代码拥有一个锁,否则处理器的时间还可以用作他用。但是在抢占式系统中,忙等待仍然有些浪费。
//更糟糕的是,如果在进入while循环之前,禁止了中断(jiffies靠内核时钟中断不断更新),jiffies值就得不到更新,
//那么while循环的判定条件就永远为真了
break;
/*2.让出处理器。如果传递给jit_fn的参数是JIT_SCHED,也就是说用户在读/proc/jitsched文件,
*此时如果time_before(jiffies, j1)宏为真,则调用schedule函数让出处理器。
*/
case JIT_SCHED:
while (time_before(jiffies, j1)) {
schedule(); //当前进程虽然放弃了CPU而不做任何事,但依然在运行队列中
}
break;
/*3.超时。如果传递给jit_fn的参数是JIT_QUEUE,也就是说用户在读/proc/jitqueue文件,
*则调用wait_event_interruptible_timeout(wait, 0, delay)函数在等待队列wait上休眠,
*但是会在指定的等待时间到期时返回。在到期之前,如果进程收到中断信号也会退出休眠返回。
*/
case JIT_QUEUE:
wait_event_interruptible_timeout(wait, 0, delay);
break;
/*4.也是超时。如果传递给jit_fn的参数是JIT_SCHEDTO,也就是说用户在读/proc/schedto文件
*则调用set_current_state将进程状态设置为TASK_INTERRUPTIBLE
*然后调用schedule_timeout (delay),schedule_timeout函数让出处理器,并等待delay指定的时间。
*实际上wait_event_interruptible_timeout函数在内部就是利用了schedule_timeout函数。
*使用schedule_timeout方式延迟,性能与使用wait_event_interruptible_timeout相似,但是可以避免声明和使用多余的等待队列
*/
case JIT_SCHEDTO:
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (delay);
break;
}
j1 = jiffies; /* actual value after we delayed */
len = sprintf(buf, "%9li %9li\n", j0, j1);
*start = buf;
return len;
}
/************************************/
短延迟:
void ndelay(unsigned long nsecs);//纳秒级延迟
void udelay(unsigned long nsecs);//微秒级延迟
void mdelay(unsigned long nsecs);//毫秒级延迟
实现原理是忙等待,类似软件中的延迟函数:
void delay(unsigned int time)
{
while(time--);
}
毫秒时延以及更大的秒时延已经比较大,内核中不应直接使用medelay()函数,将无谓的耗费CPU资源,
对于毫秒级以上时延,内核提供下述函数:
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int second);
上述函数使得调用它们的进程睡眠参数指定的时间,前两个函数不能被打断,第三个函数可以被打断。