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.