LINUX2.6.32下的进程分析

前言:

什么是进程?

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

  狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

  广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
  
Linux下的进程分析:

 

——1.组织进程——

  Linux中一般使用fork()函数创建进程;

  fork函数 

1 #include <unistd.h>
2 pid_t fork(void);

  功能:子进程复制父进程。

  特点:调用一次,返回两次。

  一个fork例子:

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 
 6 int main(void)
 7 {
 8     pid_t pid;//进程id
 9     char *message;
10     int n;
11     pid = fork();
12     if (pid < 0) {    //创建进程失败
13         perror("fork failed");
14         exit(1);
15     }
16     if (pid == 0) {    //子进程
17         message = "This is the child\n";
18         n = 6;
19     } else {     //父进程
20         message = "This is the parent\n";
21         n = 3;
22     }
23 
24     for(; n > 0; n--) {
25         printf(message);
26         sleep(1);
27     }
28     return 0;
29 }

 

一些与进程相关的函数:

  getpid/getppid

1 #include <sys/types.h>
2 #include <unistd.h>
3 
4 pid_t getpid(void);    //返回调用进程的PID号
5 pid_t getppid(void);   //返回调用进程父进程的PID号

  getuid/geteuid

1 #include <unistd.h>
2 #include <sys/types.h>
3 
4 uid_t getuid(void); //返回实际用户ID
5 uid_t geteuid(void); //返回有效用户ID

  getgid/getegid

1 #include <unistd.h>
2 #include <sys/types.h>
3 
4 gid_t getgid(void); //返回实际用户组ID
5 gid_t getegid(void); //返回有效用户组ID

  

  进程的终止

    主要涉及的函数有 _exit() , exit() , atexit() , on_exit()。 

    1. 进程的终止分为正常和异常两种。异常终止可能是由于某些信号引起的,其中的一些信号还会导致进程产生一个核心转储文件。

    2. 正常终止可以通过系统调用 _exit() 或者GNU C标准库函数exit() 实现。二者都有一个status参数,用以表示函数退出值。常用的return 函数的效果类似与 exit() 函数(这里是指在带参数的情况下,return不带参数的时候返回值则取决于C语言的版本标准以及所使用的编译器)。

    3. 不管进程正常还是异常终止,内核都会执行多个清理步骤。与系统调用 _exit() 不同的是,调用库函数 exit() 正常终止一个进程时,将会引发执行通过GNU C库函数atexit() 或 on_exit() 注册的退出处理程序(这些退出处理程序在调用函数 _exit() 或者因信号终止的情况下是不会执行的。),然后刷新stdio缓冲区。

 

 

 ——2进程状态转换——

    进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。

  一个进程从创建而产生至撤销而消亡的整个生命期间,有时占有处理器执行,有时虽可运行但分不到处理器、有时虽有空闲处理器但因等待某个事件的发生而无法执行,这一切都说明进程和程序不相同,它是活动的且有状态变化的,这可以用一组状态加以刻画。

     进程状态转换图:

     

(1)(运行态):进程是可执行的;或者正在执行,或者在运行队列中等待执行。

(2)(可中断睡眠态):进程被阻塞,等待某些条件的完成。一旦完成这些条件,内核就会将该进程的状态设置为运行态。

(3)(不可中断睡眠态):进程被阻塞,等待某些条件的完成。与可中断睡眠态不同的是,该状态进程不可被信号唤醒。

(4)(僵死态):该进程已经结束,但是其父进程还没有将其回收。

(5)(终止态):进程停止执行。通常进程在收到SIGSTOP、SIGTTIN、SIGTTOU等信号的时候会进入该状态。
 

状态切换的条件:

  就绪---->执行           调度

  执行----->就绪          时间片到

  执行------>等待         等待某个事件发生而睡眠

  等待------->就绪       因等待事情发生而唤醒

——进程调度——

    进程调度的一些方法:

 

1.      先来先去服务:

 

概念:

 

如果早就绪的进程排在就绪队列的前面,迟就绪的进程排在就绪队列的后面,那么先来先服务(FCFS: first come first service)总是把当前处于就绪队列之首的那个进程调度到运行状态。也就说,它只考虑进程进入就绪队列的先后,而不考虑它的下一个CPU周期的长短及其他因素。

 

要领:

 

按照进程进入就绪队列的先后顺序调度并分配处理机执行。先来先服务调度算法是一种非抢占式的算法,先进入就绪队列的进程,先分配处理机运行。一旦一个进程占有了处理机,它就一直运行下去,直到该进程完成工作或者因为等待某事件发生而不能继续运行时才释放处理机。

 

(1)系统只要有按FIFO规则建立的后备作业队列或就绪进程队列即可,就是一个作业控制块JCB或进程控制块PCB加入队列时加在相应队列末尾。

 

(2)调度退出队列时从相应队列首开始顺序扫描,将相关的JCB或PCB调度移出相应队列。

 

优点:有利于长作业以及CPU繁忙的作业

 

缺点:不利于短作业以及I/O繁忙的作业

 

2.      短作业(进程)优先调度算法SJ(P)F

 

概念:对预计执行时间短的作业(进程)优先分派处理机.通常后来的短作业不抢先正在执行的作业.

 

优点:

 

比FCFS改善平均周转时间和平均带权周转时间,缩短作业的等待时间;

 

提高系统的吞吐量;

 

缺点:

 

对长作业非常不利,可能长时间得不到执行;

 

未能依据作业的紧迫程度来划分执行的优先级;

 

难以准确估计作业(进程)的执行时间,从而影响调度性能。

 

3.   轮转法

 

概念:让每个进程在就绪队列中的等待时间与享受服务的时间成正比例。

 

定义:

 

时间片轮转法类似于“轮流坐庄”的思想,条件是:各作业近似认为“同时”到达,题中条件是后面作业依次比前一个作业迟到一个时间单位,分析时要严格按照RR调度算法的实现思想:系统把所有就绪进程按先入先出的原则排成一个队列。新来的进程加到就绪队列末尾。每当执行进程调度时,进程调度程序总是选出就绪队列的对首进程,让它在CPU上运行一个时间片的时间。当进程用完分给它的时间片后,调度程序便停止该进程的运行,并把它放入就绪队列的末尾。

 

4.       多级反馈队列算法

 

概念:

 

设置多个就绪队列,分别赋予不同的优先级,如逐级降低,队列1的优先级最高。每个队列执行时间片的长度也不同,规定优先级越低则时间片越长,如逐级加倍。

 

新进程进入内存后,先投入队列1的末尾,按FCFS算法调度;若按队列1一个时间片未能执行完,则降低投入到队列2的末尾,同样按FCFS算法调度;如此下去,降低到最后的队列,则按“时间片轮转”算法调度直到完成。

 

仅当较高优先级的队列为空,才调度较低优先级的队列中的进程执行。如果进程执行时有新进程进入较高优先级的队列,则抢先执行新进程,并把被抢先的进程投入原队列的末尾。

 

多级反馈队列调度算法又称反馈循环队列或多队列策略,主要思想是将就绪进程分为两级或多级,系统相应建立两个或多个就绪进程队列,较高优先级的队列一般分配给较短的时间片。处理器调度先从高级就绪进程队列中选取可占有处理器的进程,只有在选不到时,才从较低级的就绪进程队列中选取。

 

优点:

 

为提高系统吞吐量和缩短平均周转时间而照顾短进程。

 

为获得较好的I/O设备利用率和缩短响应时间而照顾I/O型进程。

 

不必估计进程的执行时间,动态调节

 

     1.LINUX下影响进程调度的因素

  Linux的调度是基于分时技术的,给每个可运行进程分配一个时间片,当进程运行结束或时间片到期时,进程就发生切换。在Linux调度算法中,每次进程切换,内核扫描可运行进程链表,计算进程的优先级,有时会用复杂的算法求出进程的当前优先级,选择“最适合”的进程运行(在未说明SMP时,默认为单处理器系统),即每个进程都有一个值与之相关联,这个值用来表示进程如何适当地分配给CPU,也就是进程的优先级。

 

 

  例子:LINUX进程调度之CFS算法

  2.1进程权重

在完全公平调度算法中,最重要的是根据优先级确定的权重,以及由权重不同产生的CPU时间。

不同优先级的权重如下:

static constint prio_to_weight[40] = {

 /* -20 */    88761,     71755,     56483,    46273,     36291,

 /* -15 */    29154,     23254,     18705,    14949,     11916,

 /* -10 */     9548,      7620,      6100,     4904,      3906,

 /*  -5*/      3121,      2501,     1991,      1586,      1277,

 /*   0*/      1024,       820,       655,       526,       423,

 /*   5*/       335,       272,       215,       172,       137,

 /*  10*/       110,        87,        70,        56,        45,

 /*  15*/        36,        29,        23,        18,       15,

};

对应优先级从100-139(也可以说是从nice值-20-19),其权重基准是0对应1024,从0提高到1,权重减少10%,从0到-1,即优先级提高一个等级,权重增加10%的CPU时间。

就绪进程有个权重的总和,当有进程启动或者加入到队列里,系统就会根据每个进程的权重算出总的权重,留待后面使用;反之,当有进程没有就绪则将其权重从总和中减掉,可参考函数inc_nr_running()和dec_nr_running()

 

 

  2.2时钟统计

 

 

 1 static void __sched_fork(struct task_struct*p)
 2 
 3 {
 4 
 5        p->se.exec_start            = 0; //进程开始运行时间
 6 
 7        p->se.sum_exec_runtime              = 0;//本次总的运行时间
 8 
 9        p->se.prev_sum_exec_runtime      = 0;//上次总的运行时间,该值在调度到另外一个进程时由当前的运行时间赋值,即se->prev_sum_exec_runtime = se->sum_exec_runtime;
10 
11 }
12 
13 在时钟中断函数中会周期执行task_tick_fair()函数,里面统计一些时间变量。
14 
15 static voidupdate_curr(struct cfs_rq *cfs_rq)
16 
17 {
18 
19        struct sched_entity *curr =cfs_rq->curr;
20 
21        u64 now = rq_of(cfs_rq)->clock;  //取得当前时间
22 
23        unsigned long delta_exec;
24 
25     ……
26 
27        delta_exec = (unsigned long)(now -curr->exec_start);  //进程执行的时间
28 
29     __update_curr(cfs_rq, curr, delta_exec);
30 
31        curr->exec_start = now;  //更新进程开始的运行时间
32 
33 …..
34 
35 }
36 
37  
38 
39 static inlinevoid
40 
41 __update_curr(structcfs_rq *cfs_rq, struct sched_entity *curr,
42 
43             unsigned long delta_exec)
44 
45 {
46 
47    ……
48 
49        curr->sum_exec_runtime += delta_exec;
50 
51 ……
52 
53        delta_exec_weighted = delta_exec;//从时钟计算中得出的运行时间
54 
55        if (unlikely(curr->load.weight !=NICE_0_LOAD)) {
56 
57               delta_exec_weighted =calc_delta_fair(delta_exec_weighted,
58 
59                                                  &curr->load);//考虑了权重以后的运行时间
60 
61        }
62 
63        curr->vruntime += delta_exec_weighted;   //该值判断进程在红黑树中的位置,该值越小,越靠近左边,越容易被重新调度到,反之,越到,越是右移,调度机会变小
64 
65 }
66 
67  
68 
69 在时钟周期中统计完时间后,就要判断该进程运行时间是否已经到达分配给它的份额:
70 
71 static void
72 
73 check_preempt_tick(structcfs_rq *cfs_rq, struct sched_entity *curr)
74 
75 {
76 
77        unsigned long ideal_runtime, delta_exec;
78 
79  
80 
81        ideal_runtime = sched_slice(cfs_rq,curr);//计算当前进程允许占用的时间。
82 
83        delta_exec = curr->sum_exec_runtime -curr->prev_sum_exec_runtime;
84 
85        if (delta_exec > ideal_runtime)
86 
87               resched_task(rq_of(cfs_rq)->curr);//设立调度标记
88 
89 }

 

 

——自己对该操作系统进程模型的看法——

  由于先前接触的操作系统就是windows,对LINUX了解不多,但是开源的LINUX方便初学者更好的深入学习操作系统,也许对LINUX具体模型搭建还不了解,但是对进程的核心有了更多了解

2018-05-01

posted @ 2018-05-01 19:13  trun  阅读(162)  评论(0编辑  收藏  举报