基于Linux 2.6.32的进程分析

前言

Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIXUNIX的多用户、多任务、支持多线程和多CPU的操作系统。

本文的分析全部基于Linux Kernel 2.6.32,源代码的链接地址:https://elixir.bootlin.com/linux/v2.6.32/source/fs

具体内容分为:

  • 进程的概念
  • 进程的建立
  • 进程的转换
  • 进程的调度
  •  对于进程的理解

一、进程的概念

1.1什么是进程


大众对进程的理解基本上基于打开任务管理器所看到的正在执行的软件等,而对于进程真的理解并不深入。

真正的进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。


1.2进程的特点


进程是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序,且具有以下结构:

动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。

 

二、进程的创建

2.1task_struct结构体

1 volatile long state;  
2 int exit_state;  

 state成员的可能取值如下: 

 1 #define TASK_RUNNING        0  
 2 #define TASK_INTERRUPTIBLE  1  
 3 #define TASK_UNINTERRUPTIBLE    2  
 4 #define __TASK_STOPPED      4  
 5 #define __TASK_TRACED       8  
 6 /* in tsk->exit_state */  
 7 #define EXIT_ZOMBIE     16  
 8 #define EXIT_DEAD       32  
 9 /* in tsk->state again */  
10 #define TASK_DEAD       64  
11 #define TASK_WAKEKILL       128  
12 #define TASK_WAKING     256  

以上进程的代表含义如下:

    TASK_RUNNING表示进程要么正在执行,要么正要准备执行。

    TASK_INTERRUPTIBLE表示进程被阻塞(睡眠),直到某个条件变为真。条件一旦达成,进程的状态就被设置为TASK_RUNNING。

    TASK_UNINTERRUPTIBLE的意义与TASK_INTERRUPTIBLE类似,除了不能通过接受一个信号来唤醒以外。

    __TASK_STOPPED表示进程被停止执行。

    __TASK_TRACED表示进程被debugger等进程监视。

    EXIT_ZOMBIE表示进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息。

    EXIT_DEAD表示进程的最终状态。

2.2进程标识符(PID)

每一个进程都通过一个唯一的进程标识值(process identification value),也就是PID,每创建一个新的线程时会赋予一个新的PID来标识一个唯一的进程。在task_struct中pid是如下表示:

1   pid_t pid;

2.3进程控制块代码分析

  1 struct task_struct {  
  2     //进程状态
  3     volatile long state;    
  4    //内存指针
  5     void *stack;    
  6     atomic_t usage; 
  7     //有几个进程正在使用该结构  
  8     unsigned int flags; 
  9     //反应进程状态的信息,但不是运行状态  
 10     unsigned int ptrace;  
 11 
 12    #ifdef CONFIG_SMP  
 13     struct task_struct *wake_entry;  
 14     int on_cpu;   //在哪个CPU上运行  
 15     #endif  
 16     int on_rq;  //on _ rq表示实体当前是否计划在运行队列中。 
 17     int prio, static_prio, normal_prio;  //静态优先级,动态优先级  
 18 /* 
 19 任务结构使用三个元素来表示进程的优先级: prio
 20 而normal _ prio表示进程的动态优先级,static _ prio表示进程的静态优先级。
 21 静态优先级是启动进程时分配给进程的优先级,它可以被修改。
 22 使用nice和sched _ setscheduler系统调用,但要在进程的运行时间。
 23 normal _ priority表示基于静态优先级和进程的调度策略。因此,相同的静态优先级将导致不同的结果
 24 */  
 25     unsigned int rt_priority;  //实时任务的优先级  
 26     const struct sched_class *sched_class;  //与调度相关的函数  
 27     struct sched_entity se; //调度实体  
 28     struct sched_rt_entity rt; //实时任务调度实体  
 29 
 30     #ifdef CONFIG_PREEMPT_NOTIFIERS  //配置抢占树,抢占的结构体的读写机制,即RCU机制。
 31     struct hlist_head preempt_notifiers; //与抢占有关的  
 32     #endif  
 33 
 34    
 35     unsigned char fpu_counter;  //包含连续上下文开关的数量
 36     #ifdef CONFIG_BLK_DEV_IO_TRACE  
 37     unsigned int btrace_seq;  
 38     #endif  
 39 
 40     unsigned int policy;  //调度策略  
 41     cpumask_t cpus_allowed;//多核体系结构中管理CPU的位图:Cpumasks provide a bitmap suitable   
 42                                //for representing the set of CPU's in a system, one bit position per CPU number.   
 43                                // In general, only nr_cpu_ids (<= NR_CPUS) bits are valid.  
 44 
 45     #ifdef CONFIG_PREEMPT_RCU  
 46     int rcu_read_lock_nesting; //RCU是一种新型的锁机制可以参考博文:http://blog.csdn.net/sunnybeike/article/details/6866473
 47     char rcu_read_unlock_special;  
 48 #if defined(CONFIG_RCU_BOOST) && defined(CONFIG_TREE_PREEMPT_RCU)  
 49     int rcu_boosted;  
 50 #endif  
 51     struct list_head rcu_node_entry;  
 52 #endif 
 53 #ifdef CONFIG_TREE_PREEMPT_RCU  
 54     struct rcu_node *rcu_blocked_node;  
 55 #endif 
 56 #ifdef CONFIG_RCU_BOOST  
 57     struct rt_mutex *rcu_boost_mutex;  
 58 #endif 
 59 #if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)  
 60     struct sched_info sched_info;   //调度相关的信息,如在CPU上运行的时间/在队列中等待的时间等。  
 61 #endif  
 62 
 63     struct list_head tasks;   //任务队列  
 64 #ifdef CONFIG_SMP  
 65     struct plist_node pushable_tasks;  
 66 #endif  
 67 
 68     struct mm_struct *mm, *active_mm;   //mm是进程的内存管理信息  
 69 /*关于mm和active_mm 
 70 lazy TLB应该是指在切换进程过程中如果下一个执行进程不会访问用户空间,就没有必要flush TLB; 
 71 kernel thread运行在内核空间,它的mm_struct指针mm是0,它不会访问用户空间。 if (unlikely(!mm))是判断切换到的新进程是否是kernel thread, 
 72 如果是,那么由于内核要求所有进程都需要一个mm_struct结构,所以需要把被切换出去的进程(oldmm)的mm_struct借过来存储在 
 73 active_mm( next->active_mm = oldmm;),这样就产生了一个anomymous user, atomic_inc(&oldmm->mm_count)就用于增加被切换进程的mm_count, 
 74 然后就利用 enter_lazy_tlb标志进入lazeTLB模式(MP),对于UP来说就这个函数不需要任何动作; 
 75 if (unlikely(!prev->mm))这句话是判断被切换出去的进程是不是kernel thread,如果是的话就要释放它前面借来的mm_struct。 
 76 而且如果切换到的进程与被切换的kernel thread的page table相同,那么就要flush与这些page table 相关的entry了。 
 77 注意这里的连个if都是针对mm_struct结构的mm指针进行判断,而设置要切换到的mm_struct用的是active_mm; 
 78 对于MP来说,假如某个CPU#1发出需要flushTLB的要求,对于其它的CPU来说如果该CPU执行kernel thread,那么由CPU设置其进入lazyTLB模式, 
 79 不需要flush TLB,当从lazyTLB模式退出的时候,如果切换到的下个进程需要不同的PageTable,那此时再flush TLB;如果该CPU运行的是普通的进程和#1相同, 
 80 它就要立即flush TLB了 
 81 
 82 大多数情况下mm和active_mm中的内容是一样的;但是在这种情况下是不一致的,就是创建的进程是内核线程的时候,active_mm = oldmm(之前进程的mm), mm = NULL, 
 83 */  
 84 #ifdef CONFIG_COMPAT_BRK  
 85     unsigned brk_randomized:1;  
 86 #endif  
 87 #if defined(SPLIT_RSS_COUNTING)  
 88     struct task_rss_stat    rss_stat;  //RSS is the total memory actually held in RAM for a process.  
 89 #endif  
 90     int exit_state;  //进程退出时的状态  
 91     int exit_code, exit_signal; //进程退出时发出的信号  
 92     int pdeath_signal;  
 93     unsigned int group_stop;   
 94     unsigned int personality;  
 95     unsigned did_exec:1;    //表示当前进程是在执行原来的代码还是在执行由execve调度的新的代码。  
 96     unsigned in_execve:1;  
 97     unsigned in_iowait:1;   
 98     unsigned sched_reset_on_fork:1;  
 99     unsigned sched_contributes_to_load:1;  
100 
101     pid_t pid;  //进程ID  
102     pid_t tgid; //线程组ID  
103 
104 #ifdef CONFIG_CC_STACKPROTECTOR  //配置堆栈保护措施
105     unsigned long stack_canary;  //canary值 保护编译器 防止堆栈溢出 导致的返回地址被填充
106 #endif  
107     struct task_struct *real_parent;   
108     struct task_struct *parent; 
109     struct list_head children;  
110     struct list_head sibling;    
111     struct task_struct *group_leader;  //线程组的头结点 
112 
113     struct list_head ptraced;   //跟踪器的头结点,跟踪器 跟踪 进程的逻辑流,即PC指令流  
114     struct list_head ptrace_entry;  
115  
116     struct pid_link pids[PIDTYPE_MAX];  
117     struct list_head thread_group;  //用来保存线程组的PID
118 
119     struct completion *vfork_done;    
120 
121         int __user *set_child_tid;  //指向用户创造创立的线程的TID号 
122         int __user *clear_child_tid;  //指向被清除的线程的TID号 
123         putime_t utime, stime, utimescaled, stimescaled;  // utime是进程用户态耗费的时间,stime是用户内核态耗费的时间。                                                         
124          //而后边的两个值应该是不同单位的时间cputime_t gtime;    
125         #ifndef CONFIG_VIRT_CPU_ACCOUNTING   
126         cputime_t prev_utime, prev_stime;  
127         #endif  
128         unsigned long nvcsw, nivcsw;  //上下文切换计数
129         struct timespec start_time;  //单调时间
130         struct timespec real_start_time;  //开机时间
131         unsigned long min_flt, maj_flt;   
132         struct task_cputime cputime_expires;  //进程到期的时间?  
133         struct list_head cpu_timers[3];  
134 
135 const struct cred __rcu *real_cred; 
136         const struct cred __rcu *cred; 
137         struct cred *replacement_session_keyring; 
138         char comm[TASK_COMM_LEN]; 
139 
140         int link_count, total_link_count;  //硬连接的数量?  
141         #ifdef CONFIG_SYSVIPC  //进程间通信相关的东西  
142         struct sysv_sem sysvsem;  
143         #endif  
144         #ifdef CONFIG_DETECT_HUNG_TASK  //挂起任务检测
145         unsigned long last_switch_count;  
146         #endif  
147 struct thread_struct thread; /*因为task_stcut是与硬件体系结构无关的,因此用thread_struct这个结构来包容不同的体系结构*/    
148         struct fs_struct *fs;  
149         struct files_struct *files;   
150         struct signal_struct *signal;  
151         struct sighand_struct *sighand;  
152         sigset_t blocked, real_blocked;  
153         sigset_t saved_sigmask; 
154         struct sigpending pending;  //表示进程收到了信号但是尚未处理。  
155         unsigned long sas_ss_sp;size_t sas_ss_size;  
156        
157         int (*notifier)(void *priv);  
158         void *notifier_data;  
159         sigset_t *notifier_mask;  
160         struct audit_context *audit_context; 
161         #ifdef CONFIG_AUDITSYSCALL  
162         uid_t loginuid;  
163         unsigned int sessionid;  
164         #endif  
165         seccomp_t seccomp;    
166         u32 parent_exec_id;   
167         u32 self_exec_id; 
168         spinlock_t alloc_lock;  
169         #ifdef CONFIG_GENERIC_HARDIRQS  //处理程序线程
170         struct irqaction *irqaction;
171         #endif 
172         struct plist_head pi_waiters;
173         struct rt_mutex_waiter *pi_blocked_on;  
174         #endif  
175         #ifdef CONFIG_DEBUG_MUTEXES //互斥死锁检测  
176         struct mutex_waiter *blocked_on;  
177         #endif  
178         #ifdef CONFIG_TRACE_IRQFLAGS  
179         unsigned int irq_events;  
180         unsigned long hardirq_enable_ip;  
181         unsigned long hardirq_disable_ip;  
182         unsigned int hardirq_enable_event;  
183         unsigned int hardirq_disable_event;  
184         int hardirqs_enabled;  
185         int hardirq_context;  
186         unsigned long softirq_disable_ip;  
187         unsigned long softirq_enable_ip;  
188         unsigned int softirq_disable_event;  
189         unsigned int softirq_enable_event;  
190         int softirqs_enabled;   
191         int softirq_context;  
192         #endif  
193         #ifdef CONFIG_LOCKDEP  
194         # define MAX_LOCK_DEPTH 48UL  
195         u64 curr_chain_key;  
196         int lockdep_depth; //锁的深度  
197         unsigned int lockdep_recursion;  
198         struct held_lock held_locks[MAX_LOCK_DEPTH];  
199         gfp_t lockdep_reclaim_gfp;  
200         #endif  
201         void *journal_info; //文件系统日志信息   
202         struct bio_list *bio_list; //块IO设备表  
203         #ifdef CONFIG_BLOCK   
204         struct blk_plug *plug;  
205         #endif  
206 
207         struct reclaim_state *reclaim_state;  
208         struct backing_dev_info *backing_dev_info;  
209         struct io_context *io_context;  
210         unsigned long ptrace_message;  
211         siginfo_t *last_siginfo;  
212         struct task_io_accounting ioac; //用于记录单个任务的IO统计信息的结构
213         #if defined(CONFIG_TASK_XACCT)  
214         u64 acct_rss_mem1;   
215         u64 acct_vm_mem1;    
216         cputime_t acct_timexpd;  
217         #endif  
218         #ifdef CONFIG_CPUSETS  
219         nodemask_t mems_allowed;    
220         int mems_allowed_change_disable;  
221         int cpuset_mem_spread_rotor;  
222         int cpuset_slab_spread_rotor;  
223         #endif  
224         #ifdef CONFIG_CGROUPS  
225         struct css_set __rcu *cgroups;  
226         struct list_head cg_list;  
227         #endif  
228         #ifdef CONFIG_FUTEX  
229         struct robust_list_head __user *robust_list;  
230         #ifdef CONFIG_COMPAT  
231         struct compat_robust_list_head __user *compat_robust_list;  
232         #endifstruct list_head pi_state_list;  
233         struct futex_pi_state *pi_state_cache;  
234         #endif  
235         #ifdef CONFIG_PERF_EVENTS  
236         struct perf_event_context *perf_event_ctxp[perf_nr_task_contexts];  
237         struct mutex perf_event_mutex;  
238         struct list_head perf_event_list;  
239         #endif  
240         #ifdef CONFIG_NUMA  
241         struct mempolicy *mempolicy;  
242         short il_next;  
243         short pref_node_fork;  
244         #endifatomic_t fs_excl; //是否允许进程独占文件系统。为0表示否。  
245         struct rcu_head rcu; //缓存上次用于拼接的管道
246         struct pipe_inode_info *splice_pipe;  
247         #ifdef CONFIG_TASK_DELAY_ACCT  
248         struct task_delay_info *delays;  
249         #endif  
250         #ifdef CONFIG_FAULT_INJECTION  
251         int make_it_fail;  
252         #endif  
253         struct prop_local_single dirties;  
254         #ifdef CONFIG_LATENCYTOP  
255         int latency_record_count;  
256         struct latency_record latency_record[LT_SAVECOUNT];  
257         #endif  
258         unsigned long timer_slack_ns;  
259         unsigned long default_timer_slack_ns;  
260         struct list_head *scm_work_list;  
261         #ifdef CONFIG_FUNCTION_GRAPH_TRACER  
262         int curr_ret_stack; //返回函数跟踪的返回地址堆栈
263         struct ftrace_ret_stack *ret_stack; //上次排程的时间戳记 
264         unsigned long long ftrace_timestamp;  
265         atomic_t trace_overrun;  
266         atomic_t tracing_graph_pause;  
267         #endif  
268         #ifdef CONFIG_TRACING  
269         unsigned long trace; //位掩码与跟踪递归计数器
270         unsigned long trace_recursion;  
271         #endif 
272         #ifdef CONFIG_CGROUP_MEM_RES_CTLR   
273         struct memcg_batch_info {
274         int do_batch;  //启动批未知时递增 
275         struct mem_cgroup *memcg; 
276         unsigned long nr_pages;  
277         unsigned long memsw_nr_pages;  
278         } memcg_batch;  
279         #endif  
280         #ifdef CONFIG_HAVE_HW_BREAKPOINT  
281         atomic_t ptrace_bp_refcnt;  
282         #endif  
283      }

2.4创建函数

1.fork() 

     fork()提供了创建进程的基本操作,可以说它是Linux系统多任务的基础。该函数在/linux-3.18.6/kernel/fork.c

2.exec系列函数

     如果只有fork(),肯定是不完美的,因为fork()只能参数一个父进程的副本。而exec系列函数则可以帮助我们建立一个全新的新进程。

     在Linux系统中,一个进程的PCB是一个C语言的结构体task_struct来表示,而多个PCB之间是由一个双向链表组织起来的,在《Understanding the Linux Kernel》中,则是进一步描述这个链表是一个双向循环链表。

     在Linux中创建一个新进程的方法是使用fork函数,fork()执行一次但有两个返回值。

     在父进程中,返回值是子进程的进程号;在子进程中,返回值为0。因此可通过返回值来判断当前进程是父进程还是子进程。

使用fork函数得到的子进程是父进程的一个复制品,它从父进程处复制了整个进程的地址空间,包括进程上下文,进程堆栈,内存信息,打开的文件描述符,信 号控制设定,进程优

先级,进程组号,当前工作目录,根目录,资源限制,控制终端等。而子进程所独有的只是它的进程号,资源使用和计时器等。可以看出,使用 fork函数的代价是很大的,它复制了

父进程中的代码段,数据段和堆栈段里的大部分内容,使得fork函数的执行速度并不快。

创建一个进程,至少涉及的函数:

sys_clone, do_fork, dup_task_struct, copy_process, copy_thread, ret_from_fork

三、进程的转换

3.1进程的状态

1、就绪状态(Ready):

     进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。

2、运行状态(Running):

     进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。

3、阻塞状态(Blocked):

    由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行。

1 #define TASK_RUNNING            0
2 #define TASK_INTERRUPTIBLE      1
3 #define TASK_UNINTERRUPTIBLE    2
4 #define TASK_STOPPED            4
5 #define EXIT_ZOMBIE            16
6 #define EXIT_DEAD              32

3.2进程状态的转化

  1. 就绪状态 -> 运行状态:处于就绪状态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪状态转换为运行状态。
  2. 运行状态 -> 就绪状态:处于运行状态的进程在时间片用完后,不得不让出处理机,从而进程由运行状态转换为就绪状态。此外,在可剥夺的操作系统中,当有更高优先级的进程就绪时,调度程度将正执行的进程转换为就绪状态,让更高优先级的进程执行。
  3. 运行状态 -> 阻塞状态:当进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行状态转换为阻塞状态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。
  4. 阻塞状态 -> 就绪状态:当进程等待的事件到来时,如I/O操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞状态转换为就绪状态。

四、进程的调度

1、CFS调度器

      之前的调度器都逐渐被淘汰, CFS是最终被内核采纳的调度器。它从RSDL/SD中吸取了完全公平的思想,不再跟踪进程的睡眠时间,也不再企图区分交互式进程。它将所有的进程都统一对待,这就是公平的含义。CFS的算法和实现都相当简单,众多的测试表明其性能也非常优越。按照作者Ingo Molnar的说法(参考Documentation/scheduler/sched-design-CFS.txt),CFS百分之八十的工作可以用一句话概括:CFS在真实的硬件上模拟了完全理想的多任务处理器。在真空的硬件上,同一时刻我们只能运行单个进程,因此当一个进程占用CPU时,其它进程就必须等待,这就产生了不公平。但是在“完全理想的多任务处理器 “下,每个进程都能同时获得CPU的执行时间,即并行地每个进程占1/nr_running的时间。例如当系统中有两个进程时,CPU的计算时间被分成两份,每个进程获得50%。假设runqueue中有n个进程,当前进程运行了10ms。在“完全理想的多任务处理器”中,10ms应该平分给n个进程(不考虑各个进程的nice值),因此当前进程应得的时间是(10/n)ms,但是它却运行了10ms。所以CFS将惩罚当前进程,使其它进程能够在下次调度时尽可能取代当前进程。最终实现所有进程的公平调度。

    核心调度器分为:
    1、周期性调度器  schedule_tick();
         周期性调度器不负责进程的切换,只是定时更新调度相关的统计信息,以备主调度器使用。
    2、主调度器      schedule();
         主调度器的工作是完成进程的切换,将CPU的使用权从一个进程切换到另一个进程。

2、调度过程

     struct task_group结构

 1  /* 进程组,用于实现组调度 */
 2   struct task_group {
 3      /* 用于进程找到其所属进程组结构 */
 4      struct cgroup_subsys_state css;
 5   
 6   #ifdef CONFIG_FAIR_GROUP_SCHED
 7      /* CFS调度器的进程组变量,在 alloc_fair_sched_group() 中进程初始化及分配内存 */
 8      /* 该进程组在每个CPU上都有对应的一个调度实体,因为有可能此进程组同时在两个CPU上运行(它的A进程在CPU0上运行,B进程在CPU1上运行) */
 9      struct sched_entity **se;
10     /* 进程组在每个CPU上都有一个CFS运行队列(为什么需要,稍后解释) */
11      struct cfs_rq **cfs_rq;
12     /* 用于保存优先级默认为NICE 0的优先级 */
13      unsigned long shares;
14 
15  #ifdef    CONFIG_SMP
16      atomic_long_t load_avg;
17      atomic_t runnable_avg;
18  #endif
19  #endif
20  
21  #ifdef CONFIG_RT_GROUP_SCHED
22      /* 实时进程调度器的进程组变量,同 CFS */
23      struct sched_rt_entity **rt_se;
24      struct rt_rq **rt_rq;
25  
26      struct rt_bandwidth rt_bandwidth;
27  #endif
28  
29      struct rcu_head rcu;
30      /* 用于建立进程链表(属于此调度组的进程链表) */
31      struct list_head list;
32  
33      /* 指向其上层的进程组,每一层的进程组都是它上一层进程组的运行队列的一个调度实体,在同一层中,进程组和进程被同等对待 */
34      struct task_group *parent;
35      /* 进程组的兄弟结点链表 */
36     struct list_head siblings;
37      /* 进程组的儿子结点链表 */
38      struct list_head children;
39 
40  #ifdef CONFIG_SCHED_AUTOGROUP
41      struct autogroup *autogroup;
42  #endif
43 
44      struct cfs_bandwidth cfs_bandwidth;
45  };

 1 struct sched_class {
 2     /* 下一优先级的调度类
 3      * 调度类优先级顺序: stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
 4      */
 5     const struct sched_class *next;
 6 
 7     /* 将进程加入到运行队列中,即将调度实体(进程)放入红黑树中,并对 nr_running 变量加1 */
 8     void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
 9     /* 从运行队列中删除进程,并对 nr_running 变量中减1 */
10     void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
11     /* 放弃CPU,在 compat_yield sysctl 关闭的情况下,该函数实际上执行先出队后入队;在这种情况下,它将调度实体放在红黑树的最右端 */
12     void (*yield_task) (struct rq *rq);
13     bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
14 
15     /* 检查当前进程是否可被新进程抢占 */
16     void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
17 
18     /*
19      * It is the responsibility of the pick_next_task() method that will
20      * return the next task to call put_prev_task() on the @prev task or
21      * something equivalent.
22      *
23      * May return RETRY_TASK when it finds a higher prio class has runnable
24      * tasks.
25      */
26     /* 选择下一个应该要运行的进程运行 */
27     struct task_struct * (*pick_next_task) (struct rq *rq,
28                         struct task_struct *prev);
29     /* 将进程放回运行队列 */
30     void (*put_prev_task) (struct rq *rq, struct task_struct *p);
31 
32 #ifdef CONFIG_SMP
33     /* 为进程选择一个合适的CPU */
34     int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags);
35     /* 迁移任务到另一个CPU */
36     void (*migrate_task_rq)(struct task_struct *p, int next_cpu);
37     /* 用于上下文切换后 */
38     void (*post_schedule) (struct rq *this_rq);
39     /* 用于进程唤醒 */
40     void (*task_waking) (struct task_struct *task);
41     void (*task_woken) (struct rq *this_rq, struct task_struct *task);
42     /* 修改进程的CPU亲和力(affinity) */
43     void (*set_cpus_allowed)(struct task_struct *p,
44                  const struct cpumask *newmask);
45     /* 启动运行队列 */
46     void (*rq_online)(struct rq *rq);
47     /* 禁止运行队列 */
48     void (*rq_offline)(struct rq *rq);
49 #endif
50     /* 当进程改变它的调度类或进程组时被调用 */
51     void (*set_curr_task) (struct rq *rq);
52     /* 该函数通常调用自 time tick 函数;它可能引起进程切换。这将驱动运行时(running)抢占 */
53     void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
54     /* 在进程创建时调用,不同调度策略的进程初始化不一样 */
55     void (*task_fork) (struct task_struct *p);
56     /* 在进程退出时会使用 */
57     void (*task_dead) (struct task_struct *p);
58 
59     /* 用于进程切换 */
60     void (*switched_from) (struct rq *this_rq, struct task_struct *task);
61     void (*switched_to) (struct rq *this_rq, struct task_struct *task);
62     /* 改变优先级 */
63     void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
64              int oldprio);
65 
66     unsigned int (*get_rr_interval) (struct rq *rq,
67                      struct task_struct *task);
68 
69     void (*update_curr) (struct rq *rq);
70 
71 #ifdef CONFIG_FAIR_GROUP_SCHED
72     void (*task_move_group) (struct task_struct *p, int on_rq);
73 #endif
74 };

调度处理函数如上:

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:  
    spin_lock_irq(&rq->lock);  
    update_rq_clock(rq);  
    clear_tsk_need_resched(prev); //清除需要调度的位   
    //state==0是TASK_RUNNING,不等于0就是准备睡眠,正常情况下应该将它移出运行队列   
    //但是还要检查下是否有信号过来,如果有信号并且进程处于可中断睡眠就唤醒它   
    //注意对于需要睡眠的进程,这里调用deactive_task将其移出队列并且on_rq也被清零   
    //这个deactivate_task函数就不看了,很简单   
    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;  
    }  
    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;  
        //完成进程切换,不讲了,跟CFS没关系   
        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);  
    if  (unlikely(reacquire_kernel_lock(current) < 0))  
        goto  need_resched_nonpreemptible;  
    preempt_enable_no_resched();  
    //这里新进程也可能有TIF_NEED_RESCHED标志,如果新进程也需要调度则再调度一次   
    if  (unlikely(test_thread_flag(TIF_NEED_RESCHED)))  
        goto  need_resched;  
}  

五、进程的理解

      通过本次对Linux2.6.32的源码分析,也是本人第一次接触到了操作系统内部,也是第一次接触如此巨大代码量的程序,从理论到实践对操作系统有了更深的认识。但是深入操作系统内核之后可以看到,其实看起清楚地看到操作系统对于进程的优化以及调度算法所付出的巨大努力,光光与调度有关的代码行数可能都已经上千行了。所有的努力都是为了能让当前系统所有的进程更高效地运行,能更好地完成人们交给计算机的任务。归结起来,就是尽量地追求效率与公平。

    进程调度时内核的重要组成部分,然而满足进程调度的各种需求,总是需要在各个方面进行平衡,很难完成完美的算法,因此进程的调度也是在不断平衡中寻求最“完美”的解决方案。调度器的不断淘汰更新表明了系统的进步。

六、参考资料

https://blog.csdn.net/melong100/article/details/6329201

https://blog.csdn.net/a2796749/article/details/47101533

https://changkun.us/archives/2015/04/183/

猜你喜欢

转载自www.cnblogs.com/lianwenjie747/p/8977737.html