进程与程序
- 程序:完成特定任务的一系列指令集合。
- 进程:程序的一次动态执行过程。(用户角度)
进程是操作系统分配资源的基本单位,也是最小单位。每个进程有自己的独立地址空间和执行状态。 操作系统内核一定要有一个PCB来管理进程。(OS角度)
程序:代码段+数据段
进程:代码段+数据段+堆栈+PCB
进程和程序的区别:
进程是动态的。程序是静态的。
进程的生命周期短暂,程序永久。一个进程只能对应一个程序,一个程序可以对应多个进程。
CPU访问外设:中断、轮询、DMA。(CPU与外设是由总线连接的)
- 并发:在一个时间段内,宏观来看有多个程序都在活动,有条不许的执行
- 并行:在每一个瞬间,都有多个程序都在同时执行,这个必须有多个CPU才醒
- 线程:是操作系统能够运行运算调度的最小单位,他被包含在进程之中,是进程中的实际运作单位,一条线程指的是进程中一个但依顺序的控制流,一个进程中可以并发多个线程,每个线程并行执行不同的任务,因为线程中个几乎不包含资源,所有执行更快,更有效率
- 简而言之,一个程序至少有一个进程,一个进程至少有一个线程,线程的划分尺度小于进程,使得多线程程序的并发性高
进程的状态
运行态:该进程正在运行,即进程正在占用CPU。
就绪态:进程已经具备执行的一切条件,正在等待分配CPU的处理时间片。
等待态:进程正在等待某个事件或某个资源。等待态又分为可中断等待和不可中断等待两种。可中断的等待进程可被信号中断,而不可中断的等待进程不能被信号中断。
停止状态:当进程收到一个SIGSTOP信号后,便由运行态进入停止状态,当收到SIGCONT信号时又会恢复运行态,该状态主要用于调试。
僵死状态(终止状态):进程已终止,但其task_struct 结构仍在内存中。顾名思义,处于这种状态的进程实际是死进程。
进程的状态转换:
进程ID
进程id PID, 父进程id PPID 每个进程都有一个非负整数表示的唯一进程ID,用于标志进程。
(一般为0~65535) 通过系统调用getpid();getppid();函数来获取
进程可以通过调用fork()函数来创建一个新进程,原来的进程称为父进程,新进程称为子进程。
fork与vfork
fork
fork从已存在的进程中创建新的进程。新进程为子进程,原进程为父进程。fork有两个返回值
父子进程共享代码,数据各自开辟空间,私有一份(采用写实拷贝)
fork()失败:内存不够,进程太多
pid_t fork(void);
//返回值:子进程中返回0,父进程中返回子进程ID,出错返回-1fork()函数被调用一次,但返回两次。其中父进程返回子进程的进程ID,
子进程返回0。子进程可以通过getppid()来获得父进程的进程ID。
vfork
vfork创建的子进程与父进程共享地址空间。
fork与vfork的区别
1.fork父子进程交替运行,vfork子进程运行,父进程阻塞,直到子进程结束
2.fork实现了写实拷贝,vfork就算写也不拷贝。
3.vfork必须用exit或excl
4.就算fork实现了写实拷贝,性能也没有vfork高
5.每个系统上的vfork都有问题,建议不要使用
僵尸进程和孤儿进程
僵尸进程:子进程结束,父进程还在,父进程没有接收到子进程的运行结果,并释放其占用的资源。
僵尸进程的主要危害:会一直占用进程ID,而进程ID是有限的。如果产生
大量的僵尸进程,将会导致进程ID耗尽。
//僵尸进程
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id > 0) //父进程
{
printf("father process[%d] is sleeping..\n",getpid());
sleep(30);
}
else
{
printf("child process[%d] is begin Z..",getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
孤儿进程:父进程提前退出,然后子进程退出,进入Z状态,子进程就称为孤儿进程。
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
//孤儿进程
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0) //子进程
{
printf("I am child process..pid=[%d]\n",getpid());
sleep(10);
}
else
{
printf("I am father process..pid=[%d]\n",getpid());
sleep(5);
exit(0);
}
return 0;
}
进程结束
正常结束: 1.main退出 退出码为0
2.exit退出
不正常结束: 1.ctrl+c
2.kill 退出码非0
3.abort()
echo $?查看退出码
退出码范围:0~255
用int的8个比特位,其余的位有别的用途,比如记录为何异常退出等情况
函数wait
函数原型:
pid_t wait (int * status);
参数说明:
status:子进程的结束状态值会由参数status 返回
pid_t:如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno 中
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
该函数我的理解是:为了避免出现僵尸进程(即父进程没有wait读取子进程的退出信息)