• Linux 进程调度通知机制



    前言

    Linux内核提供了一个任务被抢占和重新调度时的一个通知机制,这样任务可以实时的知道自己的调度状态。

    一、preempt_notifier相应API

    1.1 struct preempt_notifier

    // linux-4.10.1/include/linux/types.h
    struct hlist_node {
    	struct hlist_node *next, **pprev;
    };
    
    • 1
    • 2
    • 3
    • 4
    // linux-4.10.1/include/linux/preempt.h
    /**
     * preempt_ops - notifiers called when a task is preempted and rescheduled
     * @sched_in: we're about to be rescheduled:
     *    notifier: struct preempt_notifier for the task being scheduled
     *    cpu:  cpu we're scheduled on
     * @sched_out: we've just been preempted
     *    notifier: struct preempt_notifier for the task being preempted
     *    next: the task that's kicking us out
     */
    struct preempt_ops {
    	void (*sched_in)(struct preempt_notifier *notifier, int cpu);
    	void (*sched_out)(struct preempt_notifier *notifier,
    			  struct task_struct *next);
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    preempt_ops - 当任务被抢占和重新调度时调用的通知器。
    sched_in和out在不同的上下文中调用。在持有rq锁并禁用irq的情况下调用Sched_out,而在不启用rq锁并启用irq的情况下调用sched_in。

    /**
     * preempt_notifier - key for installing preemption notifiers
     * @link: internal use
     * @ops: defines the notifier functions to be called
     *
     * Usually used in conjunction with container_of().
     */
    struct preempt_notifier {
    	struct hlist_node link;
    	struct preempt_ops *ops;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里的 sched_in()函数和 sched_out函数是内核提供给我们开发者的接口。我们可以通过在这两个接口里面添加一些操作。
    sched_in :任务重新调度时会通知我们。
    sched_out:任务被抢占时会通知我们。
    我们可以在这两个函数里面添加简单的打印消息,这样我们就可以实时的知道任务重新调度或者被抢占了,当然你也可以添加其它的一些操作,在第三节我们会给出一个实例。

    1.2 preempt_notifier_init

    static inline void INIT_HLIST_NODE(struct hlist_node *h)
    {
    	h->next = NULL;
    	h->pprev = NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    static inline void preempt_notifier_init(struct preempt_notifier *notifier,
    				     struct preempt_ops *ops)
    {
    	INIT_HLIST_NODE(&notifier->link);
    	notifier->ops = ops;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.3 preempt_notifier_register/unregister

    static struct static_key preempt_notifier_key = STATIC_KEY_INIT_FALSE;
    
    void preempt_notifier_inc(void)
    {
    	static_key_slow_inc(&preempt_notifier_key);
    }
    EXPORT_SYMBOL_GPL(preempt_notifier_inc);
    
    void preempt_notifier_dec(void)
    {
    	static_key_slow_dec(&preempt_notifier_key);
    }
    EXPORT_SYMBOL_GPL(preempt_notifier_dec);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    // linux-4.10.1/kernel/sched/core.c
    
    /**
     * preempt_notifier_register - tell me when current is being preempted & rescheduled
     * @notifier: notifier struct to register
     */
    void preempt_notifier_register(struct preempt_notifier *notifier)
    {
    	if (!static_key_false(&preempt_notifier_key))
    		WARN(1, "registering preempt_notifier while notifiers disabled\n");
    
    	hlist_add_head(&notifier->link, &current->preempt_notifiers);
    }
    EXPORT_SYMBOL_GPL(preempt_notifier_register);
    
    /**
     * preempt_notifier_unregister - no longer interested in preemption notifications
     * @notifier: notifier struct to unregister
     *
     * This is *not* safe to call from within a preemption notifier.
     */
    void preempt_notifier_unregister(struct preempt_notifier *notifier)
    {
    	hlist_del(&notifier->link);
    }
    EXPORT_SYMBOL_GPL(preempt_notifier_unregister);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    二、schedule函数的通知机制

    2.1 schedule函数

    (1)

    // linux-4.10.1/include/linux\types.h
    
    struct hlist_head {
    	struct hlist_node *first;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    // linux-4.10.1/include/linux/sched.h
    
    struct task_struct {
    	......
    	#ifdef CONFIG_PREEMPT_NOTIFIERS
    		/* list of struct preempt_notifier: */
    		struct hlist_head preempt_notifiers;
    	#endif
    	......
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (2)
    schedule函数的通知机制在context_switch()函数上下文切换中实现:

    schedule()
    	-->__schedule()
    		-->context_switch(){
    		
    			-->prepare_task_switch()
    			
    			-->finish_task_switch()
    			
    			}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.2 prepare_task_switch

    prepare_task_switch函数中会调用sched_out,通知我们任务被抢占。

    prepare_task_switch(rq, prev, next);
    	-->fire_sched_out_preempt_notifiers(prev, next);
    	   -->__fire_sched_out_preempt_notifiers(curr, next);
    	      -->hlist_for_each_entry(notifier, &curr->preempt_notifiers, link)
    								notifier->ops->sched_out(notifier, next);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.3 finish_task_switch

    finish_task_switch函数中会调用sched_in,通知我们任务被重新调度。

    finish_task_switch(prev)
    	-->fire_sched_in_preempt_notifiers(current);
    		-->__fire_sched_in_preempt_notifiers(curr)
    			-->hlist_for_each_entry(notifier, &curr->preempt_notifiers, link)
    							notifier->ops->sched_in(notifier, raw_smp_processor_id());
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三、代码演示

    创建一个字符设备驱动,名字为my_dev,设备节点的创建将由设备文件系统负责,不需要我么来手动创建,并注册调度抢占通知机制,当被抢占和调度时打印简单的信息。

    //schdule.c
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define DEVICE_NAME "mydev"
    #define CLASS_NAME  "hello_class"
    
    static struct class *helloClass;
    static struct cdev my_dev;
    struct preempt_notifier hello_preempt;
    dev_t dev;
    
    //任务被重新调度时通知机制操作
    static void hello_sched_in(struct preempt_notifier *notifier, int cpu)
    {
    	printk("task is about to be rescheduled\n");
    	printk("\n");
    }
    
    任务被抢占时通知机制操作
    static void hello_sched_out(struct preempt_notifier *notifier,
    	struct task_struct *next)
    {
    	printk("task is just been preempted\n");
    }
    
    //注册通知回调函数
    struct preempt_ops hello_preempt_ops = {
    	.sched_in = hello_sched_in,
    	.sched_out = hello_sched_out,
    };
    
    static int my_dev_open(struct inode *inode, struct file *file){
    
        preempt_notifier_init(&hello_preempt, &hello_preempt_ops);
        preempt_notifier_inc();
    
        preempt_notifier_register(&hello_preempt);
    
        printk("open!\n");
    
        return 0;
    }
    
    static int my_dev_close(struct inode *inode, struct file *file){
    
        preempt_notifier_unregister(&hello_preempt);
    
        preempt_notifier_dec();
    
    
        printk("close!\n");
    
        return 0;
    }
    
    static const struct file_operations my_dev_fops = {
        .owner              = THIS_MODULE,
        .open               = my_dev_open,
        .release            = my_dev_close,
    };
    
    static int __init hello_init(void)
    {
        int ret;
        dev_t dev_no;
        int Major;
        struct device *helloDevice;
    
    	//动态地分配设备标识
        ret = alloc_chrdev_region(&dev_no, 0, 1, DEVICE_NAME);
    
        Major = MAJOR(dev_no);
        dev = MKDEV(Major, 0);
    
    	//初始化字符设备
        cdev_init(&my_dev, &my_dev_fops);
        //将字符设备注册到内核
        ret = cdev_add(&my_dev, dev, 1);
    
    	//创建类别class
        helloClass = class_create(THIS_MODULE, CLASS_NAME);
        if(IS_ERR(helloClass)){
            unregister_chrdev_region(dev, 1);
            cdev_del(&my_dev);
            return -1;
        }
    
    	//创建设备节点
        helloDevice = device_create(helloClass, NULL, dev, NULL, DEVICE_NAME);
        if(IS_ERR(helloDevice)){
            class_destroy(helloClass);
            unregister_chrdev_region(dev, 1);
            cdev_del(&my_dev);
    
            return -1;
        }
    
        return 0;
    
    }
    
    static void __exit hello_exit(void)
    {
        device_destroy(helloClass, dev);
        class_destroy(helloClass);
        cdev_del(&my_dev);
        unregister_chrdev_region(dev, 1);
    }
    
    module_init(hello_init);
    module_exit(hello_exit);
    
    MODULE_LICENSE("GPL");
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    //Makefile 文件
    
    obj-m := schdule.o
    
    all:
    	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
    clean:
    	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    //用户层测试文件 test.c
    
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
    	//打开字符设备驱动程序
        int fd = open("/dev/mydev", O_RDWR);
    	if(fd < 0) {
    		perror("open");		
    	}
    
    	//调用三次sleep,主动进行调度,让出CPU
        for(int i = 0; i<3; i++) {
    		sleep(1);
    	}
    
        printf("success!!\n");
    
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    让我们来看看最后的结果把:
    从结果中可以看到该任务被抢占和重新调度各有三次。
    在这里插入图片描述

    总结

    通过这个调度机制,我们就能在任务中实时的知道任务被调度出去和重新调度的时机了。

    参考资料

    Linux内核源码 4.10.0
    https://blog.csdn.net/bin_linux96/article/details/105341245

  • 相关阅读:
    【计算机视觉40例】案例20:K均值聚类实现艺术画
    MySQL中存储引擎之间的对比
    实践和项目:解决实际问题时,选择合适的数据结构和算法
    NewStarCTF 2023 web
    php公用方法
    【环境栏Composer】Composer常见问题(持续更新)
    AD22使用笔记+积累库
    Flow-vue源码中的应用
    MFC:程序的托盘显示
    关于Halcon中variation_model模型的快速解读。
  • 原文地址:https://blog.csdn.net/weixin_45030965/article/details/126372601