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;
};
// 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);
};

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;
};

这里的 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;
}
static inline void preempt_notifier_init(struct preempt_notifier *notifier,
				     struct preempt_ops *ops)
{
    
    
	INIT_HLIST_NODE(&notifier->link);
	notifier->ops = ops;
}

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);
// 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);

二、schedule函数的通知机制

2.1 schedule函数

(1)

// linux-4.10.1/include/linux\types.h

struct hlist_head {
    
    
	struct hlist_node *first;
};
// 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
	......
};

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

schedule()
	-->__schedule()
		-->context_switch(){
    
    
		
			-->prepare_task_switch()
			
			-->finish_task_switch()
			
			}

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);

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());

三、代码演示

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

//schdule.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/preempt.h>
#include <linux/device.h>
#include <linux/cdev.h>

#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");

//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

//用户层测试文件 test.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

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;
}

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

扫描二维码关注公众号,回复: 14488542 查看本文章

总结

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

参考资料

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

猜你喜欢

转载自blog.csdn.net/weixin_45030965/article/details/126372601