Process scheduling - Linux Kernel Implementation Appreciation

This content may be more, please enjoy it slowly

This is a previous analysis
http://blog.csdn.net/leesagacious/article/details/50493939

This is the waiting queue that simulates the linux kernel on github. It has not been implemented yet
https://github.com/leesagacious/wait_queue

This is the specific implementation in each subsystem
http://blog.csdn.net/leesagacious/article/details/51627608

The process needs to sleep in order to wait for some resources to be available or some events to occur,
such as completion of device initialization, the completion of I/O operations, the expiration of timers, process synchronization, the arrival of interrupts, etc.

The linux kernel uses the waiting queue to associate the process with the resources and events it is waiting for.
Isn't it often said that when your ability is still unable to control your goal, you should calm down and experience... Haha, process is also In this way, if you need resources or events, then rest on the waiting queue.

So, here comes the problem, multiple processes are waiting for the same event to occur and the resources are satisfied. If it is satisfied, what is the scenario of wake-up?
Analysis is below

One: Obviously, each Node on the queue represents a process in a sleeping state, so what is used to represent a process in a sleeping state?
It is the following struct,

/*
    等待队列上的一个元素,即 表示一个睡眠的进程
    睡眠是暂时的,睡眠的时候还持有一个被唤醒的回调函数 func

    不同睡眠状态的进程都可以在同一个队列上,这个与中断处理函数设计的不同,
    在Interrupt中,不是共享中断且触发类型不同的isr,是不会让给你挂接到isr链表上的
    中断限制的更严格点
*/
struct __wait_queue {
    /*
        这个flags很重要,涉及到一个被<<深入Linux内核架构>>称之为“惊群效应”的问题
        下面会有分析
    */
    unsigned int flags;
    /*
        0x01 很重要 !
    */
#define WQ_FLAG_EXCLUSIVE   0x01
    /*
        在初始化等待队列元素的时候 ,会指向睡眠进程的task_struct.
        看!没有等待队列元素,别想挂入等待队列,这里的等待队列元素就
        相当于要睡眠进程的一张单人床.
    */
    void *private;
    /*
        自然是唤醒函数了,这个唤醒函数可以由用户自己指定,也可以使用系统默认的唤醒函数
        这也是在初始化等待队列时,通过不同的函数来指定的.
        那么,问了,如果是用户自定义的唤醒函数,被唤醒是一个什么样的流程和场景,别急,
        下面有分析
    */
    wait_queue_func_t func;
    /*
        等待队列也是一个普通的内核链表,双向循环链表
    */
    struct list_head task_list;
};
include/linux/wait.h

wake up process

Simply draw a small flow chart to help you understand

write picture description here

Code flow appreciation:

/*
    @x
        等待队列头,你要唤醒的Process所在的等待队列的队列头

    @TASK_NORMAL
        #define TASK_NORMAL  (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
        看上面的宏,可以知道 wake_up 能唤醒这两种状态的睡眠进程
        比 TASK_ALL 少了两个

    @nr_exclusive   
        唤醒几个标志位为WQ_FLAG_EXCLUSIVE的Process
        这里是 1 ,只唤醒一个,即 找到了,就停止遍历,就brea了
        如果你要唤醒多个,可以使用另外一个函数,wake_up_nr(),它指定了唤醒的个数

    @key
        如果找到了匹配的Process,那么就会回调它持有的唤醒函数,这个key是传递给唤醒函数的参数 

    看到了把,wake_up() 的参数就表明了它要干什么、它能干什么了

    好,唤醒的条件都给你了,下面就是看__wake_up()怎么表演了,
    下面关注一下 这些参数在底层函数的活动
*/
#define wake_up(x)  __wake_up(x,TASK_NORMAL,1,NULL)

void __wake_up(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, void *key)
{
    /**
        一看到这个未初始化的flags 下面一定要spin_lock_irqsave、spin_lock_irqrestore了
    */
    unsigned long flags;
    /*
        锁     : 防止来自其他处理器的并发访问
        禁用中断: 防止来自中断处理程序的并发访问

        内核抢占呢?  
        加自旋锁了,就禁止内核抢占了,自旋锁期间,你还想发生内核抢占!!
        为什么? 看下面

        #define spin_lock_irqsave(lock, flags)              
        do {                                
            raw_spin_lock_irqsave(spinlock_check(lock), flags); 
        } while (0)

        ↓
        #define raw_spin_lock_irqsave(lock, flags)      
        do {                        
            typecheck(unsigned long, flags);    
            _raw_spin_lock_irqsave(lock, flags);    
        } while (0)

        ↓
        unsigned long __lockfunc _raw_spin_lock_irqsave(raw_spinlock_t *lock)
        {
            return __raw_spin_lock_irqsave(lock);
        }

        ↓
        static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
        {
            ...
            preempt_disable();
        }

        ↓
        #define preempt_disable() 
        do { 
            inc_preempt_count(); 
            barrier(); 
        } while (0)

        ↓   接近真相了
        #define inc_preempt_count() add_preempt_count(1)

        ↓
        #define add_preempt_count(val)  do { preempt_count() += (val); } while (0)

        ↓
        #define preempt_count() (current_thread_info()->preempt_count)

        隐藏的好深呀,这次我拿出来让它透透气、见见光,哈哈!
        具体是 + 1 的操作.解锁的时候 -1,这里不再赘述了

        看到了把,preempt_count计数器的值,决定了允不允许抢占,

        这个preempt 是一个 int 类型,下面会分析它的具体位置代表的含义

        我只想说,仅仅这个preempt的值这一点,可以一叶知秋看出调度可真够精彩的!!  还不止,还有调度类、周期性调度任务
        cpu亲和力、虚拟运行时间、进程权重、唤醒抢占、优先级、cpu负载均衡.......... 一大波重量级的嘉宾正在赶来
         哈哈!
    */
    spin_lock_irqsave(&q->lock, flags);
    /**
        受保护的这个函数,它就是遍历上图的那个链表了
        它执行的那个唤醒函数 将会是我们分析的重点
        因为,里面包含了新唤醒的这个进程能否抢占到当前运行的进程的处理
    */
    __wake_up_common(q, mode, nr_exclusive, 0, key);
    spin_unlock_irqrestore(&q->lock, flags);
}

/**
    这段代码太撩人!! 作者是哪位大神??
*/
static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
    unsigned long flags;
    int cpu, success = 0;
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325941798&siteId=291194637