第一次作业:Linux2.6源码分析进程模型

1.进程的定义

 从系统允许多个程序同时进入CPU那一天开始,我们才有了进程,进程的出现,解决了程序并发执行时对系统资源共享的描述问题,同时顺路解决了程序执行时动态特征的描述问题。

进程:一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是系统进行资源分配和调度运行的基本单位

进程四要素:

    1.有一段程序供其执行,该程序不一定是一个进程独享,也可以和其他进程共享。

    2.有进程专用的内核空间堆栈。

    3.在内核中有一个名为“进程控制块”的task_struct,内核通过结构对进程进行调度控制。

    4.有独立的用户空间。有独立的用户空间的是进程,有共享的用户空间的是用户线程,没有用户空间的是内核线程。

2.操作系统是怎么组织进程的

 进程控制块PCB(Process Control Block)是进程存在和运行的唯一标志,在Linux中用task_struct这个结构体来表示。这个结构体中有很多数据项。

2.1进程状态

task_struct中用一个长整形state volatile long state; 表示进程的状态。

在linux中有四种基本的进程状态:

(1)就绪态(TASK_RUNNING):包括了运行态的进程。这是为了方便管理,因为任意时刻处于就绪态的进程最多只有一个。

(2)等待(睡眠)态:又被分为两种

      i.浅度睡眠态(TASK_INTERRUPTIBLE): 在两种情况下被唤醒:

                                                         1.当等待的资源满足时。

                                                         2.其它进程通过信号或时钟中断唤醒。

     ii.深度睡眠态(TASK_UNINTERRUPTIBLE):只能等到等待的资源满足时才被唤醒,而不能被其它进程唤醒

 (3)暂停状态(TASK_STOPPED):收到以下几种信号,进程进入暂停状态:

        i.SIGSTOP------------------停止进程执行

        ii。SIGTSTP-----------------从终端发来信号停止进程

       iii。SIGTTIN------------------来自键盘的中断

       iv。SIGTTOU----------------后台进程请求输出。

(4)僵死状态(TASK_ZOMBIE):进程已结束且释放大部分资源,但尚未释放其PCB

2.2进程标识符

每个进程都有一个非负的唯一进程ID(PID)。虽然是唯一的,但是PID可以重用,当一个进程终止后,其他进程就可以使用它的PID了。
PID为0的进程为调度进程,该进程是内核的一部分,也称为系统进程;PID为1的进程为init进程,它是一个普通的用户进程,但是以超级用户特权运行;PID为2的进程是页守护进程,负责支持虚拟存储系统的分页操作。
linux用一个32位无符号整形pid来简单的标识一个进程,用uid和gid分别来标识进程所属的用户和组  pid_t pid; uid_t uid; gid_t gid;  。
2.3内核堆栈
 进程通过alloc_thread_info函数分配它的内核栈,通过free_thread_info函数释放所分配的内核栈.  void*stack; 
 
2.4进程链表
每个task_struct中都有一个tasks的域来连接到进程链表上去。
struct task_struct{  
  ...  
  struct list_head tasks;  
  ...  
  char comm[TASK_COMM_LEN];//可执行程序名  
  ...  
};  

2.5哈希表

进程链表是将所有的进程连接到一个链表上去,所以查找一个进程的时间复杂度是O(N),效率很低。为此,使用哈希表来提高查找的效率。

哈希表的定义  static struct hlist_head *pid_hash;  
哈希函数对查找至关重要,好的哈希函数能减少冲突发生概率。
#define pid_hashfn(nr, ns)      \  
        hash_long((unsigned long)nr + (unsigned long)ns, pidhash_shift)  
#define hash_long(val, bits) hash_32(val, bits)
static inline u32 hash_32(u32 val, unsigned int bits)  
{  
        /* On some cpus multiply is faster, on others gcc will do shifts */  
        u32 hash = val * GOLDEN_RATIO_PRIME_32;  
  
        /* High bits are more random, so use them. */  
        return hash >> (32 - bits);  
}  
/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */  
#define GOLDEN_RATIO_PRIME_32 0x9e370001UL  

2.6就绪队列

task_struct定义了一个连接到就绪队列的域run_list,同样,内核中有一个就绪队列头runqueue_head。

struct sched_rt_entity {  
        struct list_head run_list;  
        ....  
};  
struct task_struct  
{   
        ....  
        struct sched_rt_entity rt;  
        ......  
};  

2.7等待队列

等待队列的数据结构:
typedef struct __wait_queue wait_queue_t;  
struct __wait_queue {  
        unsigned int flags;  
#define WQ_FLAG_EXCLUSIVE       0x01  
        void *private;  
        wait_queue_func_t func;  
        struct list_head task_list;  
};  

等待队列列头:

struct __wait_queue_head {  
        spinlock_t lock;  
        struct list_head task_list;  
};  
typedef struct __wait_queue_head wait_queue_head_t;  

3.进程状态如何转换

在第二条中已经将进程状态做了分类,详细转换看大佬画的下图。

下附一张自己画的简图

4.进程是如何调度的

4.1进程调度优先级:

 intprio, static_prio, normal_prio;

    unsignedint rt_priority;

    conststruct sched_class *sched_class;

    structsched_entity se;

    structsched_rt_entity rt;

   

4.2优先级定义:

 #defineMAX_USER_RT_PRIO     100

    #defineMAX_RT_PRIO      MAX_USER_RT_PRIO

    #defineMAX_PRIO     (MAX_RT_PRIO+ 40)

    #defineDEFAULT_PRIO     (MAX_RT_PRIO + 20)

 实时优先级范围是0到MAX_RT_PRIO-1(即99),而普通进程的静态优先级范围是从MAX_RT_PRIO到MAX_PRIO-1(即100到139)。值越大静态优先级越低。

4.3调度策略:

 #defineSCHED_NORMAL     0

    #defineSCHED_FIFO       1

    #defineSCHED_RR     2

    #defineSCHED_BATCH      3

    /* SCHED_ISO:reserved but not implemented yet */

    #defineSCHED_IDLE       5

    /* Canbe ORed in to make sure the process is reverted back to SCHED_NORMAL on fork */

    #defineSCHED_RESET_ON_FORK     0x40000000

    SCHED_NORMAL用于普通进程,通过CFS调度器实现。

    SCHED_BATCH用于非交互的处理器消耗型进程。

    SCHED_IDLE是在系统负载很低时使用。

    SCHED_FIFO:先入先出调度算法。

    SCHED_RR:时间片轮流调度算法。

 4.4调度时机

主动式:

    当进程等待资源停止运行的时候,会处于睡眠状态,这时候直接调用schedule()请求调度,让出cpu。

    例:

 current->state= TASK_INTERRUPTIBLE

    schedule();

    使用指向当前进程状态的指针,将state改为可中断睡眠状态,然后调用schedule(),这样cpu就会调度其他资源执行。

抢占式调度:

    首先,抢占的含义,当我们一个进程A在执行的时候,B进程在执行一项更加重要的任务,这时候就需要把cpu的资源让给B,如果A不能像上面一样主动地让出,那么B就去抢占cpu的资源。

4.5调度步骤

    第一步:清理当前运行中的进程的一些资源。

    第二步:根据调度策略选择一个运行的进程。

    第三步:设置新的进程运行环境,例如堆栈,sp等。

    第四步:进程上下文切换,退出A,切到B。

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

虽然现在Linux已经是4.x时代了,但是2.6时代的跨度很大,要详细研究问题挺多的。自知才疏学浅,只粗略的学习了其进程模型,觉得其调度器的更改是一个很大改进。
Linux 一开始,普通进程和实时进程都是基于优先级的一个调度器, 实时进程支持 100 个优先级,普通进程是优先级小于实时进程的一个静态优先级,所有普通进程创建时都是默认此优先级,但可通过 nice() 接口调整动态优先级(共40个). 实时进程的调度器比较简单,而普通进程的调度器,则历经变迁。2.6 时代开始支持(2002年引入)O(1) 调度器。顾名思义,此调度器为O(1)时间复杂度。该调度器修正之前的O(n) 时间复杂度调度器,以解决扩展性问题。为每一个动态优先级维护队列,从而能在常数时间内选举下一个进程来执行。

参考链接:

https://blog.csdn.net/deep_l_zh/article/details/48346287

https://www.cnblogs.com/jacklu/p/5317406.html

https://blog.csdn.net/kklvsports/article/details/52268085

 https://www.zhihu.com/question/35484429

猜你喜欢

转载自www.cnblogs.com/sky520/p/8972433.html