内核代码阅读(26) - sleep和定时器

简介: sleep和定时器

sleep和pause

asmlinkage long sys_nanosleep(struct timespec *rqtp, struct timespec *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)
        {
                udelay((t.tv_nsec + 999) / 1000);
                return 0;
        }
        expire = timespec_to_jiffies(&t) + (t.tv_sec || t.tv_nsec);
        current->state = TASK_INTERRUPTIBLE;
        expire = schedule_timeout(expire);
        if (expire) {
                if (rmtp) {
                        jiffies_to_timespec(expire, &t);
                        if (copy_to_user(rmtp, &t, sizeof(struct timespec)))
                                return -EFAULT;
                }
                return -EINTR;
        }
        return 0;
    }
1) 参数(struct timespec *rqtp, struct timespec *rmtp)
   rqtp是输入,即要睡眠的时间
   rmtp是输出,即还有多少时间没有睡眠。
2) if (t.tv_sec == 0 && t.tv_nsec <= 2000000L && current->policy != SCHED_OTHER)
   由于时钟中断的频率是100HZ,也就是时钟的精确度是10ms,而要求睡眠的时间小于2毫秒,并且是一个实时的进程,不能让这个进程进入睡眠,因为可能10毫秒之后醒来。
   这里是通过udelay,来消耗2毫秒对应的指令数。然后就完成了睡眠,返回到用户空间了。
3) expire = timespec_to_jiffies(&t) + (t.tv_sec || t.tv_nsec);
   把要睡眠的时间转换成时钟中断的次数。
4) current->state = TASK_INTERRUPTIBLE;
   更改状态。
5) expire = schedule_timeout(expire);
   显示触发一次的调度。
6) if (expire)
   进程已经完成了睡眠,expire是剩余的中断数。转换成时间拷贝到用户空间。

udelay 消耗时间对应的指令个数

#define udelay(n) (__builtin_constant_p(n) ? \
        ((n) > 20000 ? __bad_udelay() : __const_udelay((n) * 0x10c6ul)) : \
        __udelay(n))
static void __loop_delay(unsigned long loops)
    {
        int d0;
        __asm__ __volatile__(
                "\tjmp 1f\n"
                ".align 16\n"
                "1:\tjmp 2f\n"
                ".align 16\n"
                "2:\tdecl %0\n\tjns 2b"
                :"=&a" (d0)
                :"0" (loops));
    }
最于短时间的delay,通过空转CPU实现。
有些外设要求两次外设操作之间要间隔一定的时间。

schedule_timeout

signed long schedule_timeout(signed long timeout)
    {
        struct timer_list timer;
        unsigned long expire;
        switch (timeout)
        {
        case MAX_SCHEDULE_TIMEOUT:
                schedule();
                goto out;
        default:
                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;
        init_timer(&timer);
        timer.expires = expire;
        timer.data = (unsigned long) current;
        timer.function = process_timeout;
        add_timer(&timer);
        schedule();
        del_timer_sync(&timer);
        timeout = expire - jiffies;
    out:
        return timeout < 0 ? 0 : timeout;
    }
1) add_timer(&timer);
   定时器的插入,在每次时钟中断来了后,要能够快速的找到应该处理的定时器,也就是到时时间是当前的jiffies。
2) schedule();
   主动触发一次调度,当前进程在内核态就在schedule里面切换到了另外一个进程了。
   即,当前进程在执行到schedule被切换走了,等到被唤醒才能接着执行。
3) timeout = expire - jiffies;
   执行到这条指令,说明进程已经被唤醒了。计算出剩余的时间并且返回。
   实际上这个进程已经经历了从运行到调度出去,有回到运行的过程。
4) del_timer_sync(&timer);
   进程被唤醒后把timer从全局的定时器桶中删除。
   定时器在时钟中断已经删除了一次,这个地方为什么还是删除呢?原因进程可能收到了信号,提前醒来了。定时器桶没有机会在中断的驱动下被删除。

添加一个定时器到内核的定时器队列中 - 定时器的数据结构和算法

static inline void internal_add_timer(struct timer_list *timer)
    {
        unsigned long expires = timer->expires;
        unsigned long idx = expires - timer_jiffies;
        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;
                vec = tv2.vec + i;
        } 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) {
                vec = tv1.vec + tv1.index;
        } else if (idx <= 0xffffffffUL) {
                int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
                vec = tv5.vec + i;
        } else {
                INIT_LIST_HEAD(&timer->list);
                return;
        }
        list_add(&timer->list, vec->prev);
    }
首先在内核中把要到点的时间转换成jiffies来处理。
如何来维护定时器,保证在时间点到了后快速的找到要处理的定时器呢?
内核将32位的expire分解成了 6 + 6 + 6 + 6 + 8 一共5hash表来维护。
v1是最早到时的定时器,v2次之。每次时钟中断来了之后,都从v1中取定时器,并把v1的index往下面移动一位。如果index移动了256次,则从v2中取一批定时器,如果v2的index也走完了64次,则从更高级别的v3中取一批。
这个过程很像进位制,低位的定时器消耗完了,从高位的取一批定时器,把这批定时器分散到低位的操位里。
struct timer_vec {
        int index;
        struct list_head vec[6];
    };
    struct timer_vec_root {
        int index;
        struct list_head vec[8];
    };
    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
    };

内核定时器队列的消费 - 时钟中断对定时器的处理

void timer_bh(void)
    {
        update_times();
        run_timer_list();
   }

时钟中断的bottom half处理了定时的列表。

run_timer_list

这个函数是定时器的全部逻辑,共做了两件事情:
 1) 每当低位的定时器桶走完了256格,或者64格,就从高位‘借位’。
 2) 对指针tv1.index所指向的定时器桶,遍历处理。
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) {
                        int n = 1;
                        do {
                                cascade_timers(tvecs[n]);
                        } while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);
                }
    repeat:
                head = tv1.vec + tv1.index;
                curr = head->next;
                if (curr != head) {
                        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);
                        spin_lock_irq(&timerlist_lock);
                        timer_exit();
                        goto repeat;
                }
                ++timer_jiffies; 
                tv1.index = (tv1.index + 1) & TVR_MASK;
        }
        spin_unlock_irq(&timerlist_lock);
    }
1) if (!tv1.index) {
            int n = 1;
            do {
                    cascade_timers(tvecs[n]);
            } while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);
    }
   每当tv1.index走到0的时候,就从更高位的桶里获取定时器。
2) head = tv1.vec + tv1.index;
   处理指针tv1.index所指向的定时器桶。
3) if (curr != head)
   如果tv1.index所指向的定时器列表非空,则处理所有者个桶上的定时器。

定时器对睡眠进程的唤醒process_timeout

在定时器到时后,会调用schedule_timeout设置的回调函数process_timeout
static void process_timeout(unsigned long __data)
    {
        struct task_struct * p = (struct task_struct *) __data;
        wake_up_process(p);
    }
至此,实现了进程的到时唤醒。把目标进程加入了runqueue中。
相关文章
|
5月前
|
Kubernetes 应用服务中间件 nginx
Kubernetes 使用Rook-Ceph作为持久化存储PV
本文介绍如何在 Kubernetes 中使用 Rook-Ceph 作为 PV,重点演示 CephFS 的配置与部署流程。内容涵盖前提条件、Rook-Ceph 安装、StorageClass 设置、PVC 使用示例、Ceph Dashboard 配置、测试应用(如 Nginx 和 Alpine)、ConfigMap 挂载、服务暴露、跨可用区高可用方案等关键步骤,并附有命令行操作示例和验证方法。
Kubernetes 使用Rook-Ceph作为持久化存储PV
|
机器学习/深度学习 数据采集 监控
基于YOLOv8的路面缝隙精准识别项目【完整源码数据集+PyQt5界面+完整训练流程+开箱即用!】
这是一套基于YOLOv8的路面裂缝精准识别项目,集成图形化界面(PyQt5)与完整训练流程,支持图片、视频、文件夹及摄像头多种输入方式,开箱即用。系统包含裂缝检测模型、数据集、训练代码和GUI工具,实现从训练到部署的一站式解决方案。核心优势包括高精度检测(mAP超90%)、友好的操作界面、灵活的部署方式,适合高校科研、工程实践及毕业设计。资源包含源码、预训练权重与标注数据,助力快速上手!
|
Java Spring
IntelliJ IDEA - application.yml 文件不显示 Spring 小绿叶图标而显示小网格图标解决方案
IntelliJ IDEA - application.yml 文件不显示 Spring 小绿叶图标而显示小网格图标解决方案
3573 0
IntelliJ IDEA - application.yml 文件不显示 Spring 小绿叶图标而显示小网格图标解决方案
|
负载均衡 网络协议 前端开发
一文快速上手 Nacos 注册中心+配置中心!
一文快速上手 Nacos 注册中心+配置中心!
8894 0
|
监控 安全 物联网
5G技术的革命性进步及其对社会的影响
5G技术作为移动通信领域的革命性进步,正深刻地影响着我们的生活和社会。它不仅提供了更快的数据传输速率和更低的延迟,还将引领着各个领域的创新和发展。从移动通信、工业、医疗到智能城市,5G技术正在改变着我们的世界,为未来带来更多可能性。然而,我们也需要解决一些挑战,确保5G技术的安全和可持续发展。随着技术的不断进步,5G技术的前景依然充满希望,将为我们的社会带来更多的创新和变革。
1453 1
5G技术的革命性进步及其对社会的影响
|
运维 Kubernetes Go
在k8S中,Helm优缺点是什么?
在k8S中,Helm优缺点是什么?
|
前端开发 JavaScript 开发工具
vscode教程(含使用技巧、保存时自动格式化文件等设置)
vscode教程(含使用技巧、保存时自动格式化文件等设置)
1271 0
|
Ubuntu Docker 容器
sudo apt-get update失败已经解决 报错 The update command takes no arguments
sudo apt-get update失败已经解决 报错 The update command takes no arguments
1155 0
|
机器学习/深度学习 计算机视觉
YOLOv5改进 | 检测头篇 | DynamicHead支持检测和分割(不同于网上版本,全网首发)
YOLOv5改进 | 检测头篇 | DynamicHead支持检测和分割(不同于网上版本,全网首发)
872 0