并行与并发
并行(Parallelism):多个程序在不同的处理器上同时运行。
并发(Concurrency):多个程序在单个处理器上按照一定规则进行切换, 轮流运行。由于切换迅速,给用户的感觉是每个用户独占自己的cpu,宏观上面看起来并行。
在操作系统中引入进程的目的,就是为了使多个程序能够并发执行。
进程的概念
进程(process)的概念:进程就是执行中的程序,或者说是程序的一次执行。
程序是一个静态的概念,进程是一个动态的概念
进程是操作系统资源分配和调度的基本单位。
一般操作系统进程状态
※就绪态
当进程已分配到除cpu以外的所有必要资源后,只要再获得cpu,便可立即执行。
※运行态
进程已获得cpu, 其程序正在执行。
※阻塞态
正在执行的进程由于发生某事件而暂时无法继续执行,便放弃cpu而进入阻塞状态。致使进程阻塞的典型事件有:请求I/O,申请缓冲空间等。
进程调度
一个系统中处于就绪状态的进程可能有多个,通常把它们排成一个队列,称为就绪队列。这就要求系统能按照某种算法,动态地把cpu分配给就绪队列中的一个进程,使之执行.进程调度的主要两种方式是:(1)抢占式 Linux(2)非抢占式 Mac OS 9
常见的进程调度算法:
先来先服务调度;短作业优先调度;时间片轮转法;多级反馈调度算法
进程控制块
为了描述和控制进程的运行,系统为每个进程定义了一个数据结构——进程控制块 (process control block,PCB),它是进程实体的一部分,是操作系统中最重要的记录型数据结构。
Linux的进程控制块
Linux把进程也称为任务(task),其进程控制块称为task_struct, 描述如下:
task_struct
{
long state; /*进程状态*/
int pid,uid,gid; /*一些标识符*/
struct task_struct *parent, *child, *o_sibling,*y_sibling /*一些亲属关系*/
…
}
Linux进程状态
TASK_RUNNING(运行,R):进程是可执行的;它或者正在执行,或者在运行队列中等待执行。
TASK_INTERRUPTIBLE(浅度睡眠,S):进程正在睡眠,等待某些条件的达成。一旦这些条件达成,内核就会把进程状态设置为运行。处于此状态的进程也会因为接收到信号而提前被唤醒并投入运行。
TASK_UNINTERRUPTIBLE (深度睡眠,D):除了不会因为接收到信号而被唤醒,这个状态与可打断状态相同。
TASK_ZOMBIE (僵尸,Z):该进程已经结束,但其父进程还没有调用wait4()系统调用回收该子进程的残骸。
TASK_STOPPED (停止,T):进程停止执行;进程没有投入运行也不能投入运行。
进程管理
ps (process snapshot) :查看进程状态,常用选项-ef, -aux ;top: 实时显示进程信;pstree: 显示进程树
前台进程:前台进程控制着终端,当进程没有结束时,不能执行其他命令。
Ctrl+Z:挂起(suspend)前台进程,使进程处于停止状态 (stopped)
Ctrl+C:终止前台进程
后台进程:后台进程以较低的优先级运行,那些执行无需用户干预而且很费时的任务很适合在后台运行,如编译、压缩解压等任务。
$ ./a.out &
[1] 1315
jobid pid
被挂起或在后台运行的进程,也称为作业(job)。
jobs:查看作业状态,当前进程用+号表示,而前一个进程用—号表示
当有多个被挂起的进程,不带参数时,fg命令会把当前进程带到前台,而bg命令把当前进程恢复到后台运行。
语法:fg [jobid]
作用:恢复作业号为jobid的前台进程的执行或把它从后台转移到前台执行。
提供各种服务的后台进程称为守护进程,也称为精灵进程(daemon),如提供http服务的httpd进程。
Kill 杀死一个进程, kill pid
一个用户可以杀死自己的进程,但不能杀死别人的进程.
root可以杀死任何进程
终止后台进程有两种方法:
1. kill pid
2.用fg命令把它转移到前台,然后按Ctrl+C
系统调用
系统调用(systemcall),顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口。从逻辑上来说,系统调用可被看成是一个内核与用户空间程序交互的接口。她把用户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给用户空间。
进程相关的系统调用 (CPU从用户态陷入内核态)
1、 进程的创建:fork 格式:pid=fork()
UNIX实现fork()系统调用时完成的一般步骤:
❶为子进程在进程表中分配一个空闲的proc结构;
❷赋予子进程一个唯一的进程标识(PID);
❸复制一个父进程上下文的逻辑副本;
❹增加与父进程关联的文件表和索引节点表的引用数;
❺对父进程返回子进程的标识符PID(PID大于0),对子进程返回标识符PID(子进程得到的PID=0)
fork之后父子进程并发执行。
Fork()统调用生成的子进程继承了父进程fork()之后的代码,其实质是继承了父进程的IP寄存器(指令寄存器)的内容。
2、exec 一般格式execve(pathname , argv ,envp)
注释:pathname是指要执行文件的路径名,argv是字符型指针数组,表示要执行(命令)的参数,envp也是字符型数组,表示执行程序的环境
作用:让父子进程执行不同的任务;
exec可指定一个可执行程序的副本将调用进程的正文段和数据段进行覆盖,从而使子进程执行与父进程不同的程序代码。exec有六种使用方式,常用的有execl, execv。
注:shell执行一个外部命令时就是通过创建一个子进程并让子进程执行命令
execl 表示指令用长格式调用
execl(“/bin/ls” , “ls”, “-l” ,”ab.c”, 0);
execv 可利用参数argv指针数组运行参数
execv(“pathname”, argv[0] , argv[1], argv[2],0);
execp 表示在搜索中执行程序,环境变量PATH指定的目录进行,否则在当前给定的路中搜索。
execl(“/bin/ls” , “ls”, “-l” ,”ab.c”, 0); =>excep(“ls”, “-l” ,”ab.c” ,0);
Linux系统中unistd.h中的函数声明:
3、exit 进程可通过系统调用exit终止自己的运行(自杀),使进程处于僵尸状态。
exit(status) ——>exit(1)表示发生错误后退出程序,exit(0)表示正常退出
其中status是一个整数,它可以作为进程消亡时的留言状态传递给父进程。
进程在自杀的时候可以留下遗言。这时被终止的进程会释放他所占有的资源,例如:关闭打开的文件,释放进程上下文占有的内存等并把其所有的子进程托付给init进程。处于僵死的进程只留下一个残骸(PCB),等待父进程为其处理后事。
1)孤儿进程:创建子进程的父进程已经终止,而子进程还未终止。需要等待1号进程收养,孤儿进程的父进程都是1号进程。父进程终止的子进程变换为后台进程;
2)僵死进程:父进程创建的子进程完成了它的指定任务,以交出了大部分资源,但是并为彻底清除,等待父进程使用wait系统调用对其进行回收。
※ exit和return 的区别:
exit是一个函数,有参数。exit执行完后把控制权交给系统
return是函数执行完后的返回。renturn执行完后把控制权交给调用函数。
4、wait 进程可以通过wait与正在执行的子进程终止点同步
pid=wait(stat-addr)
返回值pid是终止进程的进程号,参数stat-addr是
子进程结束时返回的状态信息存放的地址。
假设我们只对进程的同步感兴趣,而对进程返回的
状态及终止进程的pid不感兴趣的话,可以简写成
wait(0).
5、库函数system
库函数system可以实现在程序执行过程中执行一个命令或一段可执行代码。
在实际实现中,system是由fork,exec,wait及exit等相关系统调用构成的。System的内部实现包括以下几个步骤:
❶利用fork创建一个子进程。
❷调用exec,运行/bin/sh命令。
❸在子进程中利用exit调用返回值完成出错处理。
❹在父进程中调用wait,保证在制定的命令执行完毕后父进程继续拥有控制权。
❺在system函数中还用到软中断信号处理的系统调用signal.
小结:
※fork()-父亲克隆一个儿子。执行fork()之后,兵分两路,两个进程并发执行。
※exec()-新进程脱胎换骨,离家独立,开始了独立工作的职业生涯。
※wait()-等待不仅仅是阻塞自己,还准备对僵死的子进程进行善后处理。
※exit()-终止进程,把进程的状态置为“僵死”,并把其所有的子进程都托付给init进程,最后调用schedule()函数,选择一个新的进程运行。
进程的一生
○随着一句fork,一个新进程呱呱落地,但这时它只是老进程的一个克隆。然后,随着exec,新进程脱胎换骨,离家独立,开始了独立工作的职业生涯。
○人有生老病死,进程也一样,它可以是自然死亡,即运行到main函数的最后一个"}",从容地离我们而去;也可以是中途退场,退场有2种方式,一种是调用exit函数,一种是在main函数内使用return,无论哪一种方式,它都可以留下留言,放在返回值里保留下来;甚至它还可能被谋杀,被其它进程通过另外一些方式结束它的生命。
○进程死掉以后,会留下一个空壳,wait站好最后一班岗,打扫战场,使其最终归于无形。这就是进程完整的一生。