sleep与信号唤醒的问题 & 内核对信号的处理方式 & udelay
http://www.cnblogs.com/charlesblc/p/6277848.html
注意,sleep是会被信号唤醒的。
sleep函数:
#include <unistd.h> unsigned int sleep(unsigned int seconds); 此函数使调用进程被挂起,直到满足以下条件之一: 1)已经过了seconds所指定的墙上时钟时间 2)调用进程捕捉到一个信号并从信号处理程序返回 注:由于其他系统活动,实际返回时间比所要求的会迟一些,像alarm一样。 sleep的返回值: 1)在上述第一种情形中,返回值是0 2)当由于捕捉到某个信号sleep提前返回时,返回值是未睡够的时间(所要求的时间减去实际休眠时间) |
内核对信号的处理方式
参考 http://blog.csdn.net/lina_acm/article/details/51510783
内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。这里要补充的是,如果信号发送给一个正在睡眠的进程,那么要看该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。这一点比较重要,因为进程检查是否收到信号的时机是:一个进程在即将从内核态返回到用户态时;或者,在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。
感觉,sleep函数,都是可被中断的吧。不是很确定。
内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。
处理信号有三种类型:进程接收到信号后退出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似的继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。
而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)。
在BSD系统中,内核模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。
第二个要引起注意的是,如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上,这时该信号引起进程作一次longjmp,跳出睡眠状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样,但返回了一个错误代码,指出该次系统调用曾经被中断。这要注意的是,BSD系统中内核可以自动地重新开始系统调用。
具体可以参考下一篇文章:http://www.cnblogs.com/charlesblc/p/6277921.html
第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不做longjmp,一般是继续睡眠。但用户感觉不到进程曾经被唤醒,而是象没有发生过该信号一样。
第四个要注意的地方:内核对子进程终止(SIGCLD)信号的处理方法与其他信号有所区别。当进程检查出收到了一个子进程终止的信号时,缺省情况下,该进程就象没有收到该信号似的,如果父进程执行了系统调用wait,进程将从系统调用wait中醒来并返回wait调用,执行一系列wait调用的后续操作(找出僵死的子进程,释放子进程的进程表项),然后从wait中返回。SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了这个信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,那么系统调用wait的动作就有所不同,因为SIGCLD的作用仅仅是唤醒一个睡眠在可被中断优先级上的进程,那么执行wait调用的父进程被唤醒继续执行wait调用的后续操作,然后等待其他的子进程。
2、setjmp和longjmp的作用
前面在介绍信号处理机制时,多次提到了setjmp和longjmp,但没有仔细说明它们的作用和实现方法。这里就此作一个简单的介绍。
在介绍信号的时候,我们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。这种进程突然改变其上下文的情况,就是使用setjmp和longjmp的结果。setjmp将保存的上下文存入用户区,并继续在旧的上下文中执行。这就是说,进程执行一个系统调用,当因为资源或其他原因要去睡眠时,内核为进程作了一次setjmp,如果在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操作是内核为进程将原先setjmp调用保存在进程用户区的上下文恢复成现在的上下文,这样就使得进程可以恢复等待资源前的状态,而且内核为setjmp返回1,使得进程知道(注:知道是从longjmp调用的)。这就是它们的作用。
时间单位:
毫秒(ms)、微秒 (μs)、纳秒(ns)、皮秒(ps)、飞秒(fs)、阿秒、渺秒
1 s = 10^3 ms = 10^6 us = 10^9 ns = 10^12 ps = 10^15 fs=10^18阿秒=10^21渺秒=10^43普朗克常数
在Linux Driver开发中,经常要用到延迟函数:msleep,mdelay/udelay.
虽然msleep和mdelay都有延迟的作用,但他们是有区别的.
mdeday还忙等待函数(相当于for循环),在延迟过程中无法运行其他任务.这个延迟的时间是准确的.是需要等待多少时间就会真正等待多少时间.而msleep是休眠函数,它不涉及忙等待.你如果是msleep(10),那实际上延迟的时间,大部分时候是要多于10ms的,是个不定的时间值.
他们的差异,平时我也讲的出来,可是真正用起来的时候,就忘记了.曾在两个driver的i2c的code中,需要用到delay函数,而我用了msleep函数,一直I2C速度超慢.而我又不知道哪里出了问题,我潜意识中,认为我只delay了1ms,可是,实际上是十几毫秒.
这几个函数都是内核的延时函数:
1.
udelay(); mdelay(); ndelay();实现的原理本质上都是忙等待,ndelay和mdelay都是通过udelay衍生出来的,我们使用这些函数的实现往往会碰到编译器的警告implicit declaration of function'udelay',这往往是由于头文件的使用不当造成的。在include/asm-???/delay.h中定义了udelay(),而在include/linux/delay.h中定义了mdelay和ndelay.(这点弄错了吧,应该是ndelay最小吧)
udelay一般适用于一个比较小的delay,如果你填的数大于2000,系统会认为你这个是一个错误的delay函数,因此如果需要2ms以上的delay需要使用mdelay函数。
2.由于这些delay函数本质上都是忙等待,对于长时间的忙等待意味这无谓的耗费着cpu的资源,因此对于毫秒级的延时,内核提供了msleep,ssleep等函数,这些函数将使得调用它的进程睡眠参数指定的时间。
应用层:
#include <unistd.h>
1、unsigned int sleep(unsigned int seconds); 秒级
2、int usleep(useconds_t usec); 微秒级:1/10^-6
#define _POSIX_C_SOURCE 199309
#include <time.h>
3、int nanosleep(const struct timespec *req, struct timespec *rem);
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
// The value of the nanoseconds field must be in the range 0 to 999999999.
内核层:
include <linux/delay.h>
1、void ndelay(unsigned long nsecs); 纳秒级:1/10^-10
2、void udelay(unsigned long usecs); 微秒级: 1/10^-6
3、void mdelay(unsigned long msecs); 毫秒级:1/10^-3
sleep_on(), interruptible_sleep_on();
sleep_on_timeout(), interruptible_sleep_on_timeout();
根据你的情况选用这些函数,注意: sleep操作在kernel必须小心、小心。。。
udelay()等函数是cpu忙等,没有传统意义上的sleep。这些函数相当于我们平时的阻塞读、写之类的语义,主要用于等外设完成某些操作
------
nanosleep:
struct timespec
{
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
这个函数功能是暂停某个进程直到你规定的时间后恢复,参数req就是你要暂停的时间,其中req->tv_sec是以秒为单位,而tv_nsec以毫微秒为单位(10的-9次方秒)。由于调用nanosleep是是进程进入TASK_INTERRUPTIBLE,这种状态是会相应信号而进入TASK_RUNNING状态的,这就意味着有可能会没有等到你规定的时间就因为其它信号而唤醒,此时函数返回-1,切还剩余的时间会被记录在rem中。
看到这里刚刚看到他的实现是:将其状态设置成TASK_INTERRUPTIBLE,脱离就绪队列,然后进行一次进程调度再由内核在规定的时间后发送信号来唤醒这个进程。
在我刚开始学习编程时候,那时候我也曾试图使上下2条指令相隔一定时间来运行,那时我的做法是在这2条指令之间加上了一个400次的循环。这也算一种实现方式,我管它叫作延迟,但没有利用进程休眠来实现的好。但有一种特殊情况,使用休眠就无法实现了。
我们知道这里肯定脱离不了时钟中断,没有时钟中断的计时我们是无法实现这一功能的。那么假设时钟种中断是10毫秒一次(这种CPU还是有的),那么我们可以看到在函数调用的时候我们可以以毫微秒来暂停,如果我tv_sec = 0, tv_nsec = 2,那么时钟中断一定是在10微秒后来唤醒这个进程的,如果非实时性任务差个8微秒估计没什么大不了,不幸的是LINUX支持实时性任务SCHED_FIFO和SCHED_RR.(我们以前谈到过)。
这时8微秒的差距就是不能容忍了,这是就不能靠休眠和时钟中断来实现了,这是linux采用就是延迟办法,执行一个循环来达到暂停的目的。
这2种实现的差别就是休眠实现的话,进程会进入休眠状态,而延迟实现的话,CPU是在执行循环不会进入休眠态。所以可以说虽然名为nanosleep,但它不一定会使进程进入sleep状态,当然不进入sleep 态的条件太苛刻(没多少人会写实时任务,且还是暂停要小于CPU时钟频率,加上现在CPU的频率是如此之高,这种情况一般发生在要求外设中断不小于某个特定值,而且应该是采用比较老的CPU或者嵌入式中)。
唤醒问题:
msleep:睡眠之后不可唤醒;
msleep_interuptible:睡眠之后可唤醒;
ssleep:s延时,睡眠时候不可唤醒;
sleep与信号唤醒的问题 & 内核对信号的处理方式 & udelay的更多相关文章
- sleep与信号唤醒的问题 & 内核对信号的处理方式
注意,sleep是会被信号唤醒的. sleep函数:#include <unistd.h>unsigned int sleep(unsigned int seconds);此函数使调用 ...
- 深入理解Linux内核-信号
信号:1.最初被引入作为用户态进程间通信2.内核也使用信号通知进程系统所发生的事件3.信号很短,发送给进程的唯一信息通常是一个数.4.名称通常以SIG为前缀5.信号时可消费资源,每个信号只能被传递一次 ...
- Linux 信号详解六(可靠信号与不可靠信号)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h&g ...
- APUE 3 -- 信号 (signal)<II>: 可靠信号
一个事件可以事一个信号发送给一个进程,这个事件可以是硬件异常,可以是软件条件触发,可以是终端产生信号,也可以是一个kill函数调用.当信号产生后,内核通常会在进程表中设置某种形式的标志(flag).我 ...
- linux信号基本概念及如何产生信号
linux信号基本概念及如何产生信号 摘自:https://blog.csdn.net/summy_j/article/details/73199069 2017年06月14日 09:34:21 阅读 ...
- v74.01 鸿蒙内核源码分析(编码方式篇) | 机器指令是如何编码的 | 百篇博客分析OpenHarmony源码
本篇关键词:指令格式.条件域.类型域.操作域.数据指令.访存指令.跳转指令.SVC(软件中断) 内核汇编相关篇为: v74.01 鸿蒙内核源码分析(编码方式) | 机器指令是如何编码的 v75.03 ...
- (笔记)Linux内核学习(七)之内核同步机制和实现方式
一 原子操作 指令以原子的方式执行——执行过程不被打断. 1 原子整数操作 原子操作函数接收的操作数类型——atomic_t //定义 atomic_t v;//初始化 atomic_t u = AT ...
- linux内核对中断的处理方式
中断取代了轮询的通知方式,DMA取代了轮询的读写数据方式. 分类软件指令造成的中断(又叫异常,同步中断). svc, und, abt硬件通过中断请求信号造成的中断(异步中断). irq,fi ...
- Linux 内核睡眠的几种方式
译至:http://geeki.wordpress.com/2010/10/30/ways-of-sleeping-in-linux-kernel/ 在Linux中睡眠有2-3种不同的方法. 睡眠的第 ...
随机推荐
- FPGA-Xilinx原语调用之ODDR
记录背景:最近由于想实现GMIItoRGMII的功能,因此需要调用ODDR原语. ODDR:Dedicated Dual Data Rate (DDR) Output Register 通过ODDR把 ...
- 2017-2018-1 20155226 《信息安全系统设计基础》课下实践——实现mypwd
2017-2018-1 20155226 <信息安全系统设计基础>课下实践--实现mypwd 1 学习pwd命令 输入pwd命令 发现他是给出当前文件夹的绝对路径. 于是 man 1 pw ...
- 33 -jQuery 属性操作,文档操作(未完成)
- 10 腾讯云、django2.0、uwsgi、mysql、nginx 部署
1.腾讯云 操作系统 Ubuntu Server 16.04.1 LTS 64位 获取root权限 ubuntu@VM---ubuntu:~$ sudo passwd root Enter new U ...
- 【Vijos】lxhgww的奇思妙想
题面 题解 求$k$级祖先孙子 为什么要用长链剖分啊??? 倍增并没有慢多少... 其实是我不会 长链剖分做这道题还是看这位巨佬的吧. 代码 #include<bits/stdc++.h> ...
- net 快速打印日志
System.IO.File.AppendAllText(@"F:WriteText.txt", "日志内容“+"\r\n");
- nodejs 不支持 typescript (...paramName:any[])剩余参数。变相支持方式。
node es6 变相实现支持ts的剩余参数实现方式 //.ts method assign(to: any, options?: AssignOptions, ...forms: any[]){} ...
- 【Python学习笔记之三】lambda表达式用法小结
除了def语句之外,Python还提供了一种生成函数对象的表达式形式.由于它与LISP语言中的一个工具很相似,所以称为lambda.就像def一样,这个表达式创建了一个之后能够调用的函数,但是它返回了 ...
- python爬虫之解析库正则表达式
上次说到了requests库的获取,然而这只是开始,你获取了网页的源代码,但是这并不是我们的目的,我们的目的是解析链接里面的信息,比如各种属性 @href @class span 抑或是p节点里 ...
- Unity学习笔记(5):动态加载Prefab
第一种方法,从Resources文件夹读取Prefab Assets/Resources文件夹是Unity中的一个特殊文件夹,在博主当前的认知里,放在这个文件夹里的Prefab可以被代码动态加载 直接 ...