我们介绍nanosleep()和pause()两个系统调用。

系统调用nanosleep()在内核中的实现为sys_nanosleep(),代码如下:

asmlinkage long sys_nanosleep(struct timespec *rqtp, struct timespec *rmtp)//第一个指针rqtp指向给定所需睡眠时间的数据结构;第二个指针rmtp,指向返回剩余时间的数据结构
{struct timespec t;unsigned long expire;if(copy_from_user(&t, rqtp, sizeof(struct timespec)))//所需睡眠时间从用户空间复制到内核空间return -EFAULT;if (t.tv_nsec >= 1000000000L || t.tv_nsec < 0 || t.tv_sec < 0)return -EINVAL;if (t.tv_sec == 0 && t.tv_nsec <= 2000000L &&current->policy != SCHED_OTHER)//由于时钟中断只能达到10毫秒的精度,如果要求睡眠的时间小于2毫秒,而要求睡眠的进程又是个实时要求的进程,那就不能真的让这个进程进入睡眠,因为那样有可能10毫秒以后才能将其唤醒,对于实时进程来说是不能接受的    {/** Short delay requests up to 2 ms will be handled with* high precision by a busy wait for all real-time processes.** Its important on SMP not to do this holding locks.*/udelay((t.tv_nsec + 999) / 1000);//延迟两秒return 0;}expire = timespec_to_jiffies(&t) + (t.tv_sec || t.tv_nsec);//将数据结构t中的数值换算成时钟中断的次数   current->state = TASK_INTERRUPTIBLE;//将当期进程的状态设置为TASK_INTERRUPTIBLEexpire = schedule_timeout(expire);//让当期进程睡眠给定的时间;返回剩余的时钟中断次数,如果没有,返回0if (expire) {if (rmtp) {jiffies_to_timespec(expire, &t);//剩余的时钟中断次数转换成数据结构t的数值if (copy_to_user(rmtp, &t, sizeof(struct timespec)))//剩余时间从内核空间复制到用户空间return -EFAULT;}return -EINTR;}return 0;
}

schedule_timeout,让当期进程睡眠给定的时间,代码如下:

signed long schedule_timeout(signed long timeout)
{struct timer_list timer;unsigned long expire;switch (timeout){case MAX_SCHEDULE_TIMEOUT:/** These two special cases are useful to be comfortable* in the caller. Nothing more. We could take* MAX_SCHEDULE_TIMEOUT from one of the negative value* but I' d like to return a valid offset (>=0) to allow* the caller to do everything it want with the retval.*/schedule();//无限期等待goto out;default:/** Another bit of PARANOID. Note that the retval will be* 0 since no piece of kernel is supposed to do a check* for a negative retval of schedule_timeout() (since it* should never happens anyway). You just have the printk()* that will tell you if something is gone wrong and where.*/if (timeout < 0){printk(KERN_ERR "schedule_timeout: wrong timeout ""value %lx from %p\n", timeout,__builtin_return_address(0));current->state = TASK_RUNNING;goto out;}}expire = timeout + jiffies;//timeout是把需要睡眠的时间先换算成时钟中断的次数,把这个次数与当前的jiffies相加就得到了"到点"的时间init_timer(&timer);timer.expires = expire;//初始化数据结构timertimer.data = (unsigned long) current;timer.function = process_timeout;add_timer(&timer);//将timer挂入定时器队列schedule();del_timer_sync(&timer);timeout = expire - jiffies;//剩余的时钟中断次数out:return timeout < 0 ? 0 : timeout;
}
struct timer_list {struct list_head list;unsigned long expires;unsigned long data;void (*function)(unsigned long);
};

add_timer,将timer挂入定时器队列,代码如下:

void add_timer(struct timer_list *timer)
{unsigned long flags;spin_lock_irqsave(&timerlist_lock, flags);if (timer_pending(timer))goto bug;internal_add_timer(timer);spin_unlock_irqrestore(&timerlist_lock, flags);return;
bug:spin_unlock_irqrestore(&timerlist_lock, flags);printk("bug: kernel timer added twice at %p.\n",__builtin_return_address(0));
}
static inline void internal_add_timer(struct timer_list *timer)
{/** must be cli-ed when calling this*/unsigned long expires = timer->expires;unsigned long idx = expires - timer_jiffies;//期望时间点于当前时间点之差,就是中间要经过的中断次数,为32位struct list_head * vec;if (idx < TVR_SIZE) {int i = expires & TVR_MASK;vec = tv1.vec + i;} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {int i = (expires >> TVR_BITS) & TVN_MASK;//第一个expires为256,i为1vec = tv2.vec + i;//所以tv2.vec[0]为空} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;vec =  tv3.vec + i;} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;vec = tv4.vec + i;} else if ((signed long) idx < 0) {/* can happen if you add a timer with expires == jiffies,* or you set a timer to go off in the past*/vec = tv1.vec + tv1.index;} else if (idx <= 0xffffffffUL) {int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;vec = tv5.vec + i;} else {/* Can only get here on architectures with 64-bit jiffies */INIT_LIST_HEAD(&timer->list);return;}/** Timers are FIFO!*/list_add(&timer->list, vec->prev);
}
#define TVN_BITS 6
#define TVR_BITS 8
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)struct timer_vec {int index;struct list_head vec[TVN_SIZE];
};struct timer_vec_root {int index;struct list_head vec[TVR_SIZE];
};static struct timer_vec tv5;
static struct timer_vec tv4;
static struct timer_vec tv3;
static struct timer_vec tv2;
static struct timer_vec_root tv1;static struct timer_vec * const tvecs[] = {(struct timer_vec *)&tv1, &tv2, &tv3, &tv4, &tv5
};

数据结构tv1、tv2、...、tv5每个都包含了一个timer_list指针数组,这就是所谓杂凑表,表中的每个指针都指向一个定时器队列。其中tv1与其它几个数据结构的不同仅在于数组的大小,tv1的数组大小是2  ^ 8,而其它几个的大小都是2 ^ 6。这样队列中的数量是 2  ^  8 + 4 * (2 ^ 6) = 512个。

idx为32位,如下图:

如果idx小于2 ^ 8,以8位为下标链入数据结构tv1的vec[2 ^ 8]。

如果idx大于2 ^ 8小于2 ^ 14,也就是idx为14位;以14位中前6位为下标链入数据结构tv1的vec[2 ^ 6],也就是说一个队列中最多会有256个定时器;因为相同的前6位,不同的后8位,一共有256种组合,都会链入一个队列(以前6位为下标)。

以此类推......

在Linux内核源代码情景分析-中断下半部(软中断),最后时钟中断处理程序的下半部,执行timer_bh,代码如下:

void timer_bh(void)
{update_times();run_timer_list();
}
static inline void run_timer_list(void)
{spin_lock_irq(&timerlist_lock);while ((long)(jiffies - timer_jiffies) >= 0) {struct list_head *head, *curr;if (!tv1.index) {//当tv1.index为0,根据tv2.index的指引将tv2中的一个队列搬运到tv1中int n = 1;do {cascade_timers(tvecs[n]);} while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);//当tv2.index为1,根据tv3.index的指引将tv2中的一个队列搬运到tv2中}
repeat:head = tv1.vec + tv1.index;//index共256个curr = head->next;if (curr != head) {//tv1的队列struct timer_list *timer;void (*fn)(unsigned long);unsigned long data;timer = list_entry(curr, struct timer_list, list);fn = timer->function;data= timer->data;detach_timer(timer);//把定时器从队里中删除timer->list.next = timer->list.prev = NULL;timer_enter(timer);spin_unlock_irq(&timerlist_lock);fn(data);//process_timeout(current)spin_lock_irq(&timerlist_lock);timer_exit();goto repeat;}++timer_jiffies; tv1.index = (tv1.index + 1) & TVR_MASK;}spin_unlock_irq(&timerlist_lock);
}
static inline void cascade_timers(struct timer_vec *tv)
{/* cascade all the timers from tv up one level */struct list_head *head, *curr, *next;head = tv->vec + tv->index;curr = head->next;/** We are removing _all_ timers from the list, so we don't  have to* detach them individually, just clear the list afterwards.*/while (curr != head) {struct timer_list *tmp;tmp = list_entry(curr, struct timer_list, list);next = curr->next;list_del(curr); // not neededinternal_add_timer(tmp);//链入到下一级的队列curr = next;}INIT_LIST_HEAD(head);tv->index = (tv->index + 1) & TVN_MASK;
}

tv1一共有256个队列,每个队列只链入了一个定时器。把它们都执行完后,也就是tv1.index再次为0,就把tv2->vec + tv2->index这个队列这个队列的定时器,放入tv1的256个队列。我们说过tv2队列中的定时器最多是256个,所以正好链入tv1的256个队列。当tv2.index再次为1(tv2->vec + 0这个队列为空),就把tv3->vec + tv3->index这个队列的定时器,放入tv2的128个队列。依次类推。

每个被唤醒的定时器都会执行fn(data);//process_timeout(current),代码如下:

static void process_timeout(unsigned long __data)
{struct task_struct * p = (struct task_struct *) __data;wake_up_process(p);
}

被唤醒后,这个进程再次被调度执行(我们不关心在什么情况下,调度到这个进程),进程继续运行,返回schedule_timeout,又执行了一次del_timer_sync,这是因为该进程有可能被其他进程通过信号唤醒,且这个进程再次被调度执行。此事由于没有在run_timer_list执行detach_timer,所以这里补上一次。最后返回睡眠剩余的时间。

pause的系统调用是,sys_pause,代码如下:

asmlinkage int sys_pause(void)
{current->state = TASK_INTERRUPTIBLE;schedule();return -ERESTARTNOHAND;
}

和sys_nanosleep的区别是只有在接受到信号时才会被唤醒,不会定时被唤醒。

TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE最大的区别是是否在接受到信号,可以被唤醒。

Linux内核源代码情景分析-nanosleep()和pause()相关推荐

  1. Linux内核源代码情景分析笔记

    Linux内核源代码情景分析笔记 好吧,首先我承认我要是读者的话,这篇文章我看着也头疼,因为写的太长太泛(其主要部分集中在内存管理,进程管理,文件系统)!原本是想按自己理解的精简精简的,按照操作系统中 ...

  2. Linux内核源代码情景分析-内存管理

    用户空间的页面有下面几种: 1.普通的用户空间页面,包括进程的代码段.数据段.堆栈段.以及动态分配的"存储堆". 2.通过系统调用mmap()映射到用户空间的已打开文件的内容. 3 ...

  3. Linux 内核源代码情景分析(二)

    系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核 Linux 设备驱动程序 Linux设备驱动开发详解 深入理解Linux虚拟内存管理 Linux 内核源代码情景分析(一) Lin ...

  4. linux内核源代码情景分析(第一章 预备知识)

    第一章 预备知识 1.1 linux内核简介 linux发展路线图 linux目录结构 GPL许可证 GPL条款规定GNU软件以及GNU软件的基础上加以修改而成的软件,在发布.转让.出售时必须要申明该 ...

  5. Linux内核源代码情景分析-fork()

    父进程fork子进程: child = fork() fork经过系统调用.来到了sys_fork.具体过程请參考Linux内核源码情景分析-系统调用. asmlinkage int sys_fork ...

  6. linux内核调度 0号进程,Linux内核源代码情景分析---第四章 进程与进程调度

    4.1 进程四要素 什么是进程? 1:有一段代码段供其执行,这代码段不一定是进程所专用,可以与其他进程公用. 2:每个进程有其专用的系统空间的堆栈(栈)[这个栈是进程起码的"私有财产&quo ...

  7. (转载)Linux内核源代码情景分析---第四章 进程与进程调度

    原文地址: http://blog.sina.com.cn/s/blog_6b94d5680101vkiv.html 引用这篇文章主要是因为,你经常会发现不root的情况下,完全无法ping通,而又不 ...

  8. Linux 内核源代码情景分析 chap2 存储管理 (6) --- 页面的定期换出

    1. 目的 Linux 内核通过定期检查并且预先将若干页面换出, 实现减轻系统在缺页异常时候所产生的负担. 虽然, 无法避免需要临时寻找可以换出的页面, 但是, 可以减少这种事件发生的概率.Linux ...

  9. 读书笔记:《Linux内核源代码情景分析》

    第1章 预备知识 1.1 Linux内核简介 Unix.Minix.Linux Micro-Kernel.Macro-Kernel 1.2 Inter X86 CPU系列的寻址方式

最新文章

  1. 解决Tomact启动时问题 Port 8080 required by Tomcat v8.0 Server at localhost is already in use.
  2. RecyclerView.Adapter:全能notify解决方案
  3. boost::container_hash模块实现哈希序列
  4. WordPress 高颜值自适应黑/白模式Puock主题
  5. python操作xpath 0227
  6. Java包的命名规则
  7. 计算机 教育 初中 论文范文1000字,初中作文1000字
  8. asp.net程序中最常用的三十三种编程代码(转自CSDN)
  9. 一键安装服务器系统,一键安装服务器系统
  10. bulk insert 总结
  11. 谷粒商城-个人笔记(基础篇一)
  12. java 数组和集合的区别
  13. Unity-创建一个小地图
  14. u盘数据恢复的原理_U盘数据恢复其实很简单
  15. 从安防行业网络化态势 看门禁市场发展风向
  16. 西门子200smart与电流表Modbus RTU通讯
  17. 使用JAVA将m3u8转换为mp4格式
  18. [系统] Deepin系统常见问题解决(持续更新)
  19. php 抢红包_用PHP实现的抢红包小程序
  20. jsp处理的生命周期

热门文章

  1. 头文件中定义全局变量
  2. HDU 1290 献给杭电五十周年校庆的礼物 平面分割球
  3. 第39级台阶问题(递归算法)
  4. 【计算机组成原理】移位运算
  5. 2021年汽车驾驶员(高级)试题及解析及汽车驾驶员(高级)操作证考试
  6. 《雪国》《湖》读后感
  7. 苹果手机上网很慢_手机信号明明满格,为什么网速还很慢?原来是这3个功能在作怪!...
  8. mysql left join用法详解
  9. VS2013链接MYSQL数据库
  10. 做EEG脑电+脑科学研究不得不看的几本书