一、进程相关概念:
进程是资源分配的基本单位。
进程结构由以下几个部分组成:代码段、数据段、堆栈段。
代码段是静态的二进制代码,多个程序可以共享。实际上在父进程创建子进程之后,父、子进程除了pid外,几乎所有的部分几乎一样。
父、子进程共享全部数据,但并不是说他们就是对同一块数据进行操作,子进程在读写数据时会通过写时复制机制将公共的数据重新拷贝一份,之后在拷贝出的数据上进行操作。
如果子进程想要运行自己的代码段,还可以通过调用execv()函数重新加载新的代码段,之后就和父进程独立开了。我们在shell中执行程序就是通过shell进程先fork()一个子进程再通过execv()重新加载新的代码段的过程。
二、进程的创建方式:
一种是操作系统创建,一种是父进程创建。
从计算机启动到终端执行程序的过程为:0号进程 -> 1号内核进程 -> 1号用户进程(init进程) -> getty进程 -> shell进程 -> 命令行执行进程。所有创建的进程都是shell进程的子进程,这也就是为什么shell一关闭,在shell中执行的进程都自动被关闭的原因。
从shell进程到创建其他子进程需要通过以下接口:
创建进程:pid_t fork(void); 出错返回-1;父进程中返回pid > 0;子进程中pid == 0。
结束进程:void exit();
三、进程控制:
Linux进程控制:
进程地址空间:也就说32位进程的地址空间都是4G,但用户态下只能访问低3G的地址空间,若要访问3G ~ 4G的地址空间则只有进入内核态才行。
进程控制块:进程的调度实际就是内核选择相应的进程控制块,被选择的进程控制块中包含了一个进程基本的信息。
四、进程通信方式:
匿名管道、命名管道、信号量; 共享内存、消息队列、信号;socket
匿名管道:
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程之间使用。进程的亲缘关系通常是指父子进程关系
命名管道:
有名管道也是半双工的通信方式,但是允许在没有亲缘关系的进程之间使用,管道是先进先出的通信方式。
信号量:
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,实现进程、线程的对临界区的同步及互斥访问。
共享内存:
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。它往往与信号量,配合使用来实现进程间的同步和通信。
消息队列:
消息队列是有消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号:
用于通知接收进程某个事件已经发生,比如按下ctrl + C就是信号。
socket:
不同进程间通过网络通信。
五、进程调度算法:
1. 先来先服务 first-come first-serverd(FCFS)
非抢占式的调度算法,按照请求的顺序进行调度。
有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。
2. 短作业优先 shortest job first(SJF)
非抢占式的调度算法,按估计运行时间最短的顺序进行调度。
长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
3. 时间片轮转
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。
4. 优先级调度
为每个进程分配一个优先级,按优先级调度;为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。
六、进程同步方式:
1. 临界区:
对临界资源进行访问的那段代码称为临界区。为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。
2. 同步与互斥:
同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后执行关系;
互斥:多个进程在同一时刻只有一个进程能进入临界区。
3. 信号量:
信号量(Semaphore)是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。
如果信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex) ,0 表示临界区已经加锁,1 表示临界区解锁。
4.管程
管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。管程引入了 条件变量 以及相关的操作:wait() 和 signal() 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞,把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。
七、孤儿进程、僵尸进程、守护进程
孤儿进程:
如果父进程先退出,子进程还没退出,那么子进程的父进程将变为init进程。(注:任何一个进程都必须有父进程)。一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:
如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵尸进程。
父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
守护进程:
指在后台运行的,没有控制终端与之相连的进程。它独立于控制终端,周期性地执行某种任务。Linux的大多数服务器就是用守护进程的方式实现的,如web服务器进程http等;
(1)调用fork()产生一个子进程,然后使父进程退出;
(2)调用setsid()创建一个新对话期。setsid()调用成功后,进程成为新的会话组长和进程组长,并与原来的登录会话、进程组和控制终端脱离;
(3)关闭不再需要的文件描述符;
(4)将当前目录更改为根目录;
八、进程的状态
进程的五种状态:
新建态,就绪态,阻塞态,运行态,终止态