Analyze its process model according to Linux2.6.26 source code

 

1. The operating system organizes the process

1.1 Definition of Process

A process is a running activity of a program in a computer on a data set, the basic unit of resource allocation and scheduling in the system, and the basis of the operating system structure. Simply put, a process is an instance of an executing program.

1.2 Process Identifier (PID)

It is defined in linux as:

pid_t pid;

Definition of its connection to the hash table

/* PID/PID hash table linkage. */ 
struct pid_link pids[PIDTYPE_MAX];
struct list_head thread_group;

1.3 Relationship between processes

     A process has only one parent process, but can have zero, one or more child processes. In Linux, the parent process of the process, the child process and the process with the sibling relationship are defined as follows

/* 
     * pointers to (original) parent process, youngest child, younger sibling,
     * older sibling, respectively.  (p->father can be replaced with
     * p->parent->pid)
     */
    struct task_struct *real_parent; /* real parent process (when being debugged) */
    struct task_struct *parent;    /* parent process */
    /*
     * children/sibling forms the list of my children plus the
     * tasks I'm ptracing.
     */
    struct list_head children;    /* list of my children */
    struct list_head sibling;    /* linkage in my parent's children list */
    struct task_struct *group_leader;    /* threadgroup leader */

The difference between real_parent and parent is that when real_parent does not exist, the process will treat parent as its own parent process.

2. Process state transition

2.1 Process state definition

    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */

2.2 Process state classification and its assignment

#define TASK_RUNNING        0
#define TASK_INTERRUPTIBLE    1
#define TASK_UNINTERRUPTIBLE    2
#define __TASK_STOPPED        4
#define __TASK_TRACED        8
/* in tsk->exit_state */
#define EXIT_ZOMBIE        16
#define EXIT_DEAD        32
/* in tsk->state again */
#define TASK_DEAD        64
#define TASK_WAKEKILL        128

Definition of each state:

· TASK_RUNNING: The process is currently running, or is waiting for scheduling in the running queue.

TASK_INTERRUPTIBLE: The process is in a sleep state, and the process in this state is suspended because it is waiting for an event (such as waiting for a socket connection, waiting for a semaphore).

TASK_UNINTERRUPTIBLE: Uninterruptible sleep state, this process state is similar to TASK_INTERRUPTIBLE, but it will not handle signals.

TASK_STOPPED: The process has terminated execution, it is not running, and cannot be run.

TASK_TRACED: The process will enter this state when it is being monitored by other processes such as the debugger.

 

EXIT_ZOMBIE : The process has terminated, it is waiting for its parent process to collect some statistics about it.

· EXIT_DEAD: final state (as the name suggests). When a process is removed from the system, it enters this state because its parent process has collected all statistics via wait4() or waitpid() calls.

2.3 Process state transition

3. Process scheduling

3.1 Definition of scheduling:

When a computer system has more than ten programming systems, there are usually multiple processes or threads competing for the CPU at the same time. This happens whenever two or more processes are in the ready state. If only one CPU is available, then the next process to run must be selected. In an operating system, this part of the work that does the selection is called the scheduler , and the algorithm used by this program is called the scheduling algorithm .

3.2 Two scheduling algorithms of Linux system

3.2.1 Linux O(1) scheduler (O(1) scheduler)

       This scheduler is able to perform task scheduling in constant time, such as selecting a task from the execution queue or adding a task to the execution queue, independent of the total number of tasks in the system.

      O(1) scheduler advantage: can perform task scheduling in constant time

      O(1) Scheduler Disadvantage: Using heuristics to determine the interactivity of a task can make the task's prioritization complex and imperfect, resulting in poor performance when dealing with interactive tasks.

3.2.2CFS (Completely Fair Scheduler)

      It was first integrated into the kernel in Linux 2.6.23     

The main idea of       ​​CF S is to use a red-black tree as the data structure of the scheduling queue.

      Summary of CFS 's scheduling algorithm : The algorithm always prioritizes the tasks that use the least CPU time, usually the tasks on the leftmost node in the tree. CFS will periodically increment its virtual time running value according to the time the task has been running, and compare this value with the value of the current leftmost node in the tree, if the running task still has a small virtual running time value, Then it will continue to run, otherwise, it will be inserted in the proper place in the red-black tree, and the CPU will execute the task on the new leftmost node. 

         The definition of the schedulable entity of a process in Linux 2.2.26 is as follows:

/*
 * CFS stats for a schedulable entity (task, task-group etc)
 *
 * Current field usage histogram:
 *
 *     4 se->block_start
 * 4 se-> run_node
 *     4 se->sleep_start
 *     6 se->load.weight
 */
struct sched_entity {
    struct load_weight    load;        /* for load-balancing */
    struct rb_node        run_node;
    struct list_head    group_node;
    unsigned int        on_rq;

    u64            exec_start;
    u64            sum_exec_runtime;
    u64 vruntime;
    u64            prev_sum_exec_runtime;

    u64            last_wakeup;
    u64            avg_overlap;

#ifdef CONFIG_SCHEDSTATS
    u64            wait_start;
    u64            wait_max;
    u64            wait_count;
    u64            wait_sum;

    u64            sleep_start;
    u64            sleep_max;
    s64            sum_sleep_runtime;

    u64            block_start;
    u64            block_max;
    u64            exec_max;
    u64            slice_max;

    u64 nr_migrations;
    u64            nr_migrations_cold;
    u64            nr_failed_migrations_affine;
    u64            nr_failed_migrations_running;
    u64            nr_failed_migrations_hot;
    u64            nr_forced_migrations;
    u64            nr_forced2_migrations;

    u64            nr_wakeups;
    u64            nr_wakeups_sync;
    u64            nr_wakeups_migrate;
    u64            nr_wakeups_local;
    u64            nr_wakeups_remote;
    u64            nr_wakeups_affine;
    u64            nr_wakeups_affine_attempts;
    u64            nr_wakeups_passive;
    u64            nr_wakeups_idle;
#endif

#ifdef CONFIG_FAIR_GROUP_SCHED
    struct sched_entity    *parent;
    /* rq on which this entity is (to be) queued: */
    struct cfs_rq        *cfs_rq;
    /* rq "owned" by this entity/group: */
    struct cfs_rq        *my_q;
#endif
};

Among them, the vruntime (virtual running time)           of the task running on the CPU is defined . In the red-black tree, each internal node in the tree corresponds to a task, and the child nodes on the left correspond to less running time on the CPU. tasks, the child nodes on the right are those tasks that have so far consumed more CPU time. In Linux, the calculation formula of vruntime is:

      vruntime=actual running time*1024/process weight=(scheduling period*process weight/all process weights)*1024/process weight=scheduling period*1024/total weight of all processes

       Therefore, we can conclude that although the weights of the processes are different, their vruntime growth rate should be the same regardless of the weight, so that we can choose the next running process more fairly through vruntime.

        The following is the active scheduling code schedule

/*
 * schedule() is the main scheduler function.
 */
asmlinkage void __sched schedule(void)
{
    struct task_struct *prev, *next;
    unsigned long *switch_count;
    struct rq *rq;
    int cpu;

need_resched:
    preempt_disable();
    cpu = smp_processor_id();
    rq = cpu_rq(cpu);
    rcu_qsctr_inc(cpu);
    prev = rq->curr;
    switch_count = &prev->nivcsw;

    release_kernel_lock(prev);
need_resched_nonpreemptible:

    schedule_debug(prev);

    hrtick_clear(rq);

    /*
     * Do the rq-clock update outside the rq lock:
     */
    local_irq_disable();
    update_rq_clock(rq);
    spin_lock(&rq->lock);
    clear_tsk_need_resched(prev);

    if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
        if (unlikely(signal_pending_state(prev->state, prev)))
            prev->state = TASK_RUNNING;
        else
            deactivate_task(rq, prev, 1);
        switch_count = &prev->nvcsw;
    }

#ifdef CONFIG_SMP
    if (prev->sched_class->pre_schedule)
        prev->sched_class->pre_schedule(rq, prev);
#endif

    if (unlikely(!rq->nr_running))
        idle_balance(cpu, rq);

    prev->sched_class->put_prev_task(rq, prev);
    next = pick_next_task(rq, prev);

    if (likely(prev != next)) {
        sched_info_switch(prev, next);

        rq->nr_switches++;
        rq->curr = next;
        ++*switch_count;

        context_switch(rq, prev, next); /* unlocks the rq */
        /*
         * the context switch might have flipped the stack from under
         * us, hence refresh the local variables.
         */
        cpu = smp_processor_id();
        rq = cpu_rq(cpu);
    } else
        spin_unlock_irq(&rq->lock);

    hrtick_set(rq);

    if (unlikely(reacquire_kernel_lock(current) < 0))
        goto need_resched_nonpreemptible;

    preempt_enable_no_resched();
    if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))
        goto need_resched;
}

The focus is the pick_next_task function

/*
 * Pick up the highest-prio task:
 */
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev)
{
    const struct sched_class *class;
    struct task_struct *p;

    /*
     * Optimization: we know that if all tasks are in
     * the fair class we can call that function directly:
     */
    if (likely(rq->nr_running == rq->cfs.nr_running)) {
        p = fair_sched_class.pick_next_task(rq);
        if (likely(p))
            return p;
    }

    class = sched_class_highest;
    for ( ; ; ) {
        p = class->pick_next_task(rq);
        if (p)
            return p;
        /*
         * Will never be NULL as the idle class always
         * returns a non-NULL p:
         */
        class = class->next;
    }
}

4. Your own views on the operating system process model

         In an operating system, the existence of each process and the corresponding activities are very complex and numerous, and there are many relationships between each process. In my opinion, the current process model has done a good job and can ensure that the current process model we use the load level of the system.

 

Guess you like

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