一、进程与程序的区别
程序是一段存放在存储介质上的一系列代码和数据的映像,是一个静态的实体。
进程是执行起来的程序,是一个动态地实体,还包括它管理的资源(如打开的文件,挂起的信号,地址空间等等)。
二、进程四要素
1.有一段程序供其执行,该程序不一定是一个进程独享,也可以和其他进程共享。
2.有进程专用的内核空间堆栈。
3.在内核中有一个名为“进程控制块”的task_struct,内核通过结构对进程进行调度控制。
4.有独立的用户空间。有独立的用户空间的是进程,有共享的用户空间的是用户线程,没有用户空间的是内核线程。
三、进程状态
进程有三态:就绪太,执行态和阻塞态。当进程被创建以后,就处于一个就绪太,等待cpu的调度,当cpu执行它的时候就处于执行态,在执行过程中进程比如访问别的资源,获取锁,信号量不成功,或者产生了中断,进程就会处于阻塞状态,当中断结束,得到锁和信号量之后进程就又从阻塞态变为就绪态,等待调度执行。
比较重要的进程状态有以下三种:
TASK_RUNNING(可执行状态)
只有在该状态的进程才可能在CPU上运行。而同一时刻可能有多个进程处于可执行状态,这些进程的task_struct结构(进程控制块)被放入对应CPU的可执行队列中(一个进程最多只能出现在一个CPU的可执行队列中)。进程调度器的任务就是从各个CPU的可执行队列中分别选择一个进程在该CPU上运行。
TASK_INTERRUPTIBLE(可中断睡眠状态)
处于这个状态的进程因为等待某某事件的发生(比如等待socket连接、等待信号量),而被挂起。这些进程的task_struct结构被放入对应事件的等待队列中。当这些事件发生时(由外部中断触发、或由其他进程触发),对应的等待队列中的一个或多个进程将被唤醒。事实上,我们的很多进程大多时间都在这个状态。。。
TASK_UNINTERRUPTIBLE/TASK_KILLABLE(不可中断睡眠状态)
与TASK_INTERRUPTIBLE状态类似,进程处于睡眠状态,但是此刻进程是不可中断的,即使给他sigkill也不会中断,不会响应异步信号。这个状态有个缺点,就是假如这个进程一直处于这种状态,那么它就会占用cpu资源,使得其它进程一直处于等待状态,但是引入这个状态时为了对一些内核操作进行保护,比如与硬件设备进行交互的时候,为了避免此短暂的交互过程被打断,需要引入这个状态。为了对这个状态进行改进,linux2.6.25之后引入了 TASK_KILLABLE,该状态和TASK_UNINTERRUPTIBLE是类似的,但是它可以被致命信号SIGKILL唤醒。
其他的进程状态包括:
TASK_TRACED:处于被调试状态的进程。
TASK_DEAD:进程退出时所处的状态。
EXIT_ZOMBIE:表示进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信。
四、Linux进程描述
Linux进程描述主要是是task_struct这个结构体,在/include/linux下的sched.h文件中,在第1215行:
1.状态描述:
volatile long state;
int exit_state;
状态有哪些我们上面已经分析过了。
2.进程标识符:
pid_tpid;
pid_ttgid;
这个pid就是每个进程都有一个标号,系统中有最大32768个进程,给每一个进程都分配一各进程号便于管理,Linux将一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID存放在tgid成员中。
3.内核堆栈:
void*stack;
进程通过alloc_thread_info函数分配它的内核栈,通过free_thread_info函数释放所分配的内核栈,这里不做深究。
4.进程亲属关系:
structtask_struct *real_parent; /* real parent process*/
structtask_struct *parent; /* recipient of SIGCHLD,wait4() reports */
/*
* children/sibling forms the list of mynatural children
*/
structlist_head children; /* list of my children */
structlist_head sibling; /* linkage in my parent's children list */
structtask_struct *group_leader; /* threadgroup leader */
看到这个命名有点想笑,还有真爸爸和爸爸的区分(进程也会喜当爹吗?)
在Linux中,所有进程之间都有着直接或间接地联系,每个进程都有其父进程,也可能有零个或多个子进程。拥有同一父进程的所有进程具有兄弟关系。
5.进程调度优先级:
intprio, static_prio, normal_prio;
unsignedint rt_priority;
conststruct sched_class *sched_class;
structsched_entity se;
structsched_rt_entity rt;
优先级定义在这里:
#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)。值越大静态优先级越低。
五、Linux进程调度
1.调度策略
在sched.h中调度策略有以下几种:
#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:时间片轮流调度算法。
其中SCHED_FIFO和SCHED_RR是实时调度算法,具体算法思想比较简单,顾名思义,具体实现这里不做深究。
2.调度时机
主动式:
当进程等待资源停止运行的时候,会处于睡眠状态,这时候直接调用schedule()请求调度,让出cpu。
例:
current->state= TASK_INTERRUPTIBLE
schedule();
使用指向当前进程状态的指针,将state改为可中断睡眠状态,然后调用schedule(),这样cpu就会调度其他资源执行,当然这个过程比较复杂,因为还涉及到当前状态的保存,进程资源是否回收等等。
抢占式调度:
首先,抢占的含义,当我们一个进程A在执行的时候,B进程在执行一项更加重要的任务,这时候就需要把cpu的资源让给B,如果A不能像上面一样主动地让出,那么B就去抢占cpu的资源。Linux2.4只支持用户态抢占,2.6既支持用户态抢占,也支持内核态抢占,这是2.6实时性提高的一个重要原因。当然也有非抢占机制,它的优势是中断响应很快,几乎不需要信号量来保护共享数据,但是明显的缺点就是实时性较差。2.6为什么要支持内核态抢占呢?因为有些进程或者线程一旦运行到内核态,就会一直运行,不出来,那它就会一直占有cpu的资源,其他紧急的进程或者线程就会处于等待状态,这样实时性就大大降低了。所以2.6开始允许优先级更高的进程在内核态中进行抢占。打个比方,贪官携款潜逃去从中国去了美国(进程A占有cpu资源从用户态进去了内核态),他就一直待在美国,没有引渡条例啊,这时候纪检的人很着急的要找他问话,怎么办呢?等呗。。。万一人家一辈子不回来了呢?有些事就不能处理了。所以现在国家正在积极的和欧美商讨引渡条例,也就是我们的2.6支持内核态抢占啦。
用户态抢占发生的时机:
1.从系统调用返回用户空间。
2.从中断处理程序返回用户空间。
3.当某个进程耗尽它的时间片的时候。
4.当一个优先级更高的进程处于可执行状态的时候。
以上抢占情况发生时,还要满足need_resched被设置这个条件,这是一个触发条件。
内核态抢占发生的时机:
1.中断处理程序完成,返回内核空间之前。(贪官从美国(内核态)跑回中国来(用户态)来看爹妈老婆,返回去之前,被抓了(抢占)。。。)
2.当内核代码解锁和使用软中断的时候,这时候再次具有课抢占性。
不允许内核抢占时机:
1.内核正在运行中断处理程序。
在Linux内核中进程不能抢*断(中断只能被其他中断中止、抢占,进程不能中止、抢*断),在中断例程中不允许进行进程调度。进程调度函数schedule()会对此作出判断,如果是在中断中调用,会打印出错信息。
2.内核正在进行中断上下文处理。
硬件中断返回前会执行软中断,此时仍然处于中断上下文中。
3.持有锁的时候不应被抢占,如自旋锁,读写锁。
内核中的这些锁是为了在SMP系统中短时间内保证不同CPU上运行的进程并发执行的正确性。当持有这些锁时,内核不应该被抢占,否则由于抢占将导致其他CPU长期不能获得锁而死等。
4.内核正在执行调度程序schedule的时候。
抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序。
5.内核正在对每个CPU“私有”的数据结构操作。
在SMP中,对于per-CPU数据结构未用spinlocks保护,因为这些数据结构隐含地被保护了(不同的CPU有不一样的per-CPU数据,其他CPU上运行的进程不会用到另一个CPU的per-CPU数据)。但是如果允许抢占,但一个进程被抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题,这时应禁抢占。当然这里对于我们的s3c2440是不存在的。
那么linux为了不让上述情况被抢占,设置了preempt_count抢占计数,被设置在进程的thread_info结构中。preempt_count()函数用于获取preempt_count的值,preemptible()用于判断内核是否可抢占。当进程进入上述状态时,就+1,退出时就-1,根据它的值来判断是否可抢占。
3.调度步骤
第一步:清理当前运行中的进程的一些资源。
第二步:根据调度策略选择一个运行的进程。
第三步:设置新的进程运行环境,例如堆栈,sp等。
第四步:进程上下文切换,退出A,切到B。
笔记做到这里,如有不正确的地方还请指出,大家共同进步。