在单处理器系统上,在给定时刻只有一个程序可以运行,在多处理器系统上,可以真正并行运行的进程数据,取决于物理CPU的数目;
进程优先级
在比较粗糙的划分中,进程分为实时进程和非实时进程;
1. 硬实时进程具有严格的时间限制,某些任务必须在指定的时间内完成;
2. 软实时进程是硬实时进程的一种弱化形式,尽快需要快速得到结果,但是仍然可以稍微延迟执;
3. 大多数进程是没有特定时间约束的普通进程,它们可以通过重要性来分配优先级;
下面为CPU时间分配图,进程的运行按照时间片调度,分给进程的时间片份额与其相对重要性相当;系统中的时间流动对应圆盘转动,CPU则对应圆周旁的扫描器;最终效果是,尽管所有进程都有机会运行,但是重要的进程会比次要的进程得到更多的CPU时间;
这种方式称之为抢占式多任务处理;各个进程都得到一定的时间运行,在运行时间到期后,内核会从进程收回控制权,让一个不同的进程运行,而不考虑前一个进程执行的上一个任务;被抢占进程的运行环境都会保存起来,在该进程恢复执行时,其进程环境也完全恢复;时间片长度会根据进程的重要性不同而变化;
进程生命周期
当调度器在进程之间切换时,必须知道系统中每个进程的状态;
进程可能有以下几种状态:
1. 运行:此刻进程正在执行;
2. 等待:进程能够运行,但是没有得到许可,因为CPU分配给了另外一个进程,调度器可以在下一次任务切换时选择该进程;
3. 睡眠:该进程正在睡眠,无法运行,因为它在等待一个外部事件,调度器无法在下一次任务切换时选择该进程;
4. 终止:程序执行终止后的状态;
5. 僵尸:子进程终止,但是父进程没有调用wait4回收资源;
路径1:进程必须等待某个事件发生,状态由运行态切换到睡眠态;
路径2:调度器从该进程上收回cpu资源时,状态由运行态切换到等待态;
路径3:进程无法从睡眠态之间切换到运行态,而是在所等待的事件发生后,先变回等待态;
路径4:调度器分配CPU时间给当前进程,则其状态由等待态切换到运行态;
路径5:程序执行终止后,状态由运行态切换到终止态;
抢占式多任务处理
Linux进程管理结构中还需要两种状态:用户态和内核态;
进程通常都处于用户态,只能访问他们自身的数据,无法干扰系统中其他应用程序;
如果进程想要访问系统的数据或者功能,需要从用户态切换到内核态;切换通过下面两种方式:
1. 系统调用,由进程主动发起,陷入内核态;
2. 中断,自动触发,其发生的次数多少不可预测;
内核的抢占调度模型建立了下面的层次,来判断哪些进程状态可被其他状态抢占;
1. 普通进程总可能被抢占,甚至由其他进程抢占;
2. 核心态的系统调用,其他进程无法抢占,但是中断可以抢占系统调用;
3. 中断可以抢占用户态和内核态的进程,其具有最高的优先级;
进程状态task_struct->state
内核涉及到进程相关的算法都围绕着task_struct结构建立,其成员将进程和内核子系统联系起来;
进程状态,涉及字段如下:
1 struct task_struct { 2 volatile long state; 3 }
state指定了进程的当前状态,可使用下面之之一:
TASK_RUNNING-进程处于可运行状态,进程可能已经由调度器分配了CPU时间正在运行,也可能在等待调度器进行下一次调度自己;该状态确保进程可以立即运行,无需等待外部事件;
TASK_INTERRUPTIBLE-进程需要等待某事件或者其他资源,进入可中断的睡眠状态,在内核发送信号给该进程表明事件已发生时,进程状态变为TASK_RUNNING,只要调度器选中该进程即可恢复执行;
TASK_UNINTERUPTIBLE-用于因内核指示而停用,进入不可中断的睡眠状态;这个状态的进程不能由外部信号唤醒,只能由内核亲子唤醒;
TASK_STOPPED-表示进程特意停止运行;
TASK_TRACED-用于区分调试进程和正常进程;
下面常量既可用于state的进程状态字段,也可用于exit_state字段,后者明确的用于退出状态;
EXIT_ZOMBIE-僵尸状态;
EXIT_DEAD-wait系统调用已经发出,而进程完全从系统移除之前的状态,只有多个线程对同一个进程发出wait调用时,该状态才有意义;
进程资源限制state->signal->rlim[]
Linux提供资源限制机制,对进程使用系统资源施加某些限制,其结构定义如下:
1 struct rlimit { 2 __kernel_ulong_t rlim_cur; 3 __kernel_ulong_t rlim_max; 4 };
rlim_cur-进程当前资源限制,称之为软限制;
rlim_max-该限制的最大允许值,称之为硬限制;
系统调用setrlimit来增减当前限制,但不能超过rlim_max指定的值,getrlimit系统调用用于检查当前限制;
1 #include <sys/time.h> 2 #include <sys/resource.h> 3 4 int getrlimit(int resource, struct rlimit *rlim); 5 int setrlimit(int resource, const struct rlimit *rlim);
进程资源限制参数如下:
如果某类资源没有使用限制(默认设置),则将rlmi_max设置为RLIM_INFINITY;例外情况如下:
1. 打开文件的数目-默认值是1024;
2. 每个用户的最大进程数,定义为max_threads/2;max_threads是一个全局变量,指定了在把八分之一可用内存用于管理线程信息的情况下,可以创建的线程数目;在计算时,提前给定了20个线程的最小可能的内存用量;
进程的产生
新进程使用fork和exec系统调用产生
1. fork生成当前进程的一个相同副本,该副本称之为子进程;
2. exec从一个可执行的二进制文件加载一个应用程序,来替代当前运行的进程;
Linux还提供了clone系统调用,与fork类似,但新进程不独立于父进程,可以与父进程共享某些资源,可以指定需要共享和复制的资源种类;clone用于实现线程,但仅仅该系统调用还不足以做到这点,还需要用户空间库才能完整的实现线程;
进程的退出
进程必须用exit系统调用终止,这使得内核有机会将该进程使用的资源释放回系统;该函数的实现就是将各个引用计数减1,如果引用计数为0,则说明没有进程继续使用对应的结构,那么将相应的内存区域返还给内存管理模块;
进程ID号
进程总会分配一个号码用于其命名空间的唯一标识,称为PID;用fork或者clone产生的每个进程都有内核自动的分配了一个新的唯一的PID;
分配一个空闲的PID,本质上等同于寻找位图中第一个值0的比特,接下来将该比特设置为1;反之,释放一个PID可以通过将对应的比特从1设置为0来实现;
除了PID之外,还有以下几种ID类型:
1. 线程组ID(TGID):处于某个线程组中的所有进程都有统一的线程组ID;如果进程没有线程组,那么其PID和TGID相同;线程组中的主进程被称为组长,通过clone创建的线程的task_struct->group_leader成员指向组长的task_struct;
2. 进程组ID:独立进程可以合并成进程组(使用setpgrp系统调用),进程组成员的task_struct->pgrp属性都是相同的,即进程组组长的PID;进程组简化了向组所有成员发送信号的操作;另,用管道连接的进程包含在同一个进程组中;
3. 会话ID:几个进程组可以合并成一个会话;会话中所有进程都有相同的会话ID,保存在task_struct->session成员中;会话ID可以使用setsid系统调用设置;
对于命名空间而言,子命名空间中的所有PID对父命名空间是可见的,但是子命名空间无法看到父命名空间的PID;为此,需要区分局部ID和全局ID;
全局ID:内核本身和初始命名空间中的唯一ID号;
局部ID:属于某个特定命名空间,不剧本全局有效性;