Linux内核提供了一个任务被抢占和重新调度时的一个通知机制,这样任务可以实时的知道自己的调度状态。
// 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:任务被抢占时会通知我们。
我们可以在这两个函数里面添加简单的打印消息,这样我们就可以实时的知道任务重新调度或者被抢占了,当然你也可以添加其它的一些操作,在第三节我们会给出一个实例。
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(¬ifier->link);
notifier->ops = ops;
}
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(¬ifier->link, ¤t->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(¬ifier->link);
}
EXPORT_SYMBOL_GPL(preempt_notifier_unregister);
(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()
}
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);
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
#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");
//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
#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;
}
让我们来看看最后的结果把:
从结果中可以看到该任务被抢占和重新调度各有三次。
通过这个调度机制,我们就能在任务中实时的知道任务被调度出去和重新调度的时机了。
Linux内核源码 4.10.0
https://blog.csdn.net/bin_linux96/article/details/105341245