在kernel中延时的办法查到两种:
void udelay(unsigned long usecs)
void mdelay(unsigned long msecs)
精确延时微秒, 毫秒, 系统通过空转一定的时钟周期实现, 在系统启动的时候获得cpu频率。
这种办法实际会执行一个汇编的LOOP,虽然达到了短时间的延时,但究其本质应该是属于忙等待,并未release cpu.
schedule_timeout
使task睡眠一定的时间, 时间到了之后, 将会被kernel唤醒, 不精确。
/* set task’s state to interruptible sleep */
set_current_state(TASK_INTERRUPTIBLE);
/* take a nap and wake up in “s” seconds */
schedule_timeout(s * HZ);
schedule_timeout中设置超时例程为process_timeout, 后者只是负责唤醒该进程, data中存放的是current进程号
init_timer(&timer); //timer 初始化
timer.expires = expire; //这个是要sleep的值
timer.data = (unsigned long) current;
timer.function = process_timeout;//这个是时间到了后要执行的函数,这里就是wake_up_process()
add_timer(&timer);
schedule();
del_timer_sync(&timer); //进程被唤醒后从此开始执行, 确保timer被删除. 如果是超时, 则kernel会删除之, 但是如果进程是被信号唤醒而不是因为超时, 就需要在这里删除
timeout = expire - jiffies;
return timeout
这样的用法要注意一个地方: set_current_state(TASK_UNINTERRUPTIBLE);
因为如果task is interruptible, 可能会在timeout之前被信号唤醒,就达不到sleep指定时间的目的了。
关于jiffies变量:
全局变量jiffies用来记录自启动以来产生的节拍的总数。系统启动时会将该变量初始化为0,此后,每当时钟中断产生时就会增加该变量的值。jiffies和另外一个变量息息相关:HZ。HZ是每秒系统产生的时钟中断次数,所以jiffies每秒增加的值也就是HZ;在x86体系结构中,内核版本在2.4以前的值为100,在2.6内核中被定义为1000。 jiffies的定义:
extern unsigned long volatile jiffies; //定义于
从定义可以看出,jiffies的类型为unsigned long,在32位体系结构上unsigned long是32位,在64位体系结构上是64位。 在32位体系结构上,在系统的HZ值为100的情况下,jiffies的回绕时间大约为500天左右,如果HZ为1000,那么回绕时间将只有50天左右。如果发生了回绕现象,对内核中直接利用jiffies来比较时间的代码将产生很不利的影响,比如在<>一书中有一个例子可以说明这个问题:
unsigned long timeout = jiffies + HZ/2; //0.5后超时
/*执行一些任务*/
........
/*然后检查时间是否过长*/
if(timeout>jiffies){
/*没有超时...*/
}else{
/*超时了....*/
}
在这个例子中,如果设置了timeout后发生了回绕,那么第一个判断条件将变为真,这与实际情况不符,尽管因为实际的时间比timeout要大,但因为jiffies发生了回绕变成了0,所以jiffies肯定小于timeout的值。 内核也专门针对这种情况提供了四个宏来帮助比较jiffies计数:
#define time_after(unknown,known) ((long)(known) - (long)(unknown)<0)
#define time_before(unkonwn,known) ((long)(unknown) - (long)(known)<0)
#define time_after_eq(unknown,known) ((long)(unknown) - (long)(known)>=0)
#define time_before_eq(unknown,known) ((long)(known) -(long)(unknown)>=0)
这些宏看起来很奇妙,只是将计数值强制转换为long类型然后比较就能避免回绕带来的问题,这是为什么呢?这和计算机中数据存储的形式有关!!
计算机中数据的存储是按照二进制补码的方式存储的,之所以采用补码的方式存储是因为这样可以简化运算规则和线路设计。另外一个相关的概念就是原码,原码采用一个最高位作为符号位,其余位是数据大小的二进制表示。 补码的定义是这样的:正数的补码即为原码,负数的补码为原码除符号位外其他各位取反再加1。举例如下:
[+1]补码 = [+1]原码 = 0000 0001
[- 1]补码 = [- 1]原码取反+1 = 1111 1110 + 1 = 1111 1111
而c语言中的数据类型相当于在代码和实际机器的存储之间的一个中间层,机器中存储的数据,如果按照不同的类型格式取读取就会得到不同的结果,才代码和实际存储之间,编译器充当了翻译者的角色。这是编译器能实现多种数据类型和强制类型转换的基础。
有了这些基础后,就不难理解上述宏定义的巧妙之处了,为了便于说明,以下假设jiffies是单字节的无符号数,范围为0~255。假如jiffies开始为250,由于是无符号数据,那么它在机器中实际存储的补码为11111010,记为J1;timeout如果被设为252,实际存储为11111100;而过了一会jiffies发生回绕编变成了1,实际存储变为00000001,记为J2。 那么此时如果按照无符号数比较其大小关系,有: J1
J1如果按照有符号数读取,首先从补码转换成原码:10000110,转换成十进制为-6;
timeout按照有符号数读取,首先从补码转换成原码:10000100,转换成十进制为-4;
J2按照有符号数读取,首先从补码转换成原码:00000001,转换成十进制为1;
这样它们的大小关系为: J1
关于jiffies变量定义的另外一点就是volatile关键字的使用,volatile本义是易变的。这个关键字用于通知编译器不要针对变量做优化,每次读取都要重新从实际的内存地址去读取。 为什么会有这种情况发生? 编译器如果发现一个频繁使用的变量在当前代码中没有被改写过,就可能会把它加载到寄存器中去,以便获得比访问内存更快的速度。 jiffies是个全局变量会被中断处理程序频繁改写,因此加上这个关键字非常有必要,同时也给我们定义全局变量提了一个醒^_^
jiffies是一个全局变量,又被多处使用,很明显应该属于临界资源,但是在访问该变量的时候并没有什么加锁等操作,这又是为何呢? 这牵涉到另外一个知识点:原子操作。 原子操作指的是该操作是一次性完成的,不会被其他进程打断。在32位体系结构中,32位以下的数据存取都是原子操作,在操作它们的时候不必担心会被打断,针对其他的高级数据类型内核也提供了一些接口来进行原子操作。
阅读(4027) | 评论(0) | 转发(0) |