程序和进程的区别:
程序:是静态的,存放在磁盘上的可执行文件。
进程:是动态的,是运行在内存中的程序的执行实例。
程序是一些指令的有序集合,而进程是程序执行的实例,进程是程序的一次执行过程。进程的状态是变化的,其中包括进程的创建、调度和消亡。只要程序运行,此时就是进程,程序每运行一次就会创建一个进程。
在Linux系统中,进程时管理事务的基本单元。进程拥有自己独立的处理环境和系统资源(处理器、存储器、I/O设备、数据、程序)。
进程整个生命周期可以分为三个状态:
就绪态:
进程已经具备执行的一切条件,正在等待分配CPU的处理时间。
执行态:
该进程正在占用CPU运行。
等待态:
进程因不具备某些执行条件而暂时无法继续执行的状态。
进程的调度机制:
时间片的轮转,上下文的切换。
多进程的执行是交替执行的,一个进程执行一段时间,然后再进行下一个进程在执行一段时间,以此类推,执行到最后一个再回到第一个。
进程三个状态的转换关系:
进程控制卡就是用于保存一个进程信息的结构体,又称之为PCB。
OS是根据PCB来对并发执行的进程进行控制和管理的。系统在创建一个进程的时候会开辟一段内存空间存放与此进程相关的PCB数据结构。PCB是操作系统中最重要的记录型数据结构。PCB中记录了用于描述进程进展情况及控制进程运行所需的全部信息。PCB是进程存在的唯一标志,在Linux中PCB存放在task_struct结构体中。
PCB结构体中的部分数据:
调度数据
进程的状态、标志、优先级、调度策略等。
时间数据
创建该进程的时间、在用户态的运行时间、在内核态的运行时间等。
文件系统数据
umask掩码、文件描述符表等。内存数据、进程上下文、进程标识(进程号)...
每一个进程都由一个进程号来标识,其类型为pid_t,进程号的范围0~32767
进程号是由操作系统随机给当前进程分配的,不能自己控制
进程号总是唯一的,但是进程号可以重用,当一个进程结束后,其进程号还可以再次使用。
Ubuntu中查看当前系统中所有开启的进程:
ps ajx
其中:
PPID:当前进程号的父进程号
PID:当前进程的进程号
PGID:当前进程所在的进程组ID
COMMAND:当前进程的名字
特殊的进程号:
在linux系统中进程号由0开始。
进程号为0及1的进程由内核创建。
进程号为0的进程通常是调度进程,常被称为交换进程(swapper)。
进程号为1的进程通常是init进程,init进程是所有进程的祖先。
除调度进程外,在linux下面所有的进程都由进程init进程直接或者间接创建。
进程号(PID)
标识进程的一个非负整型数。
父进程号(PPID)
任何进程(除init进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进程号(PPID)。
进程组号(PGID)
进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID) 。
Linux操作系统中提供了三个获得进程号的函数:getpid()、getppid()、getpgid()。
#include
#include
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程的进程号
pid_t getpgid(pid_t pid);
功能:获取当前进程所在进程组的id
案例:
结果:
#include
pid_t fork(void);
功能:在已有的进程基础上再创建一个子进程
参数:wu
返回值:
成功:大于0,子进程的进程号,标识父进程的代码区(父进程返回的是子进程的进程号)。
等于0,子进程的代码区。(子进程返回0)
失败:-1 (返回给父进程,子进程不会创建)
使用fork函数的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间
地址空间:
包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号 等。 子进程所独有的只有它的进程号,计时器等。因此,使用fork函数的代价是很大的。
fork函数执行完毕后父子进程的空间示意图:
结果:
结果:
结果:
#include
unsigned int sleep(unsigned int seconds);
功能:进程在一定的时间内没有任何动作,称为进程的挂起(进程处于等待态)
参数:
seconds:指定要挂起的秒数
返回值:
若进程挂起到sec指定的时间则返回0,若有信号中断则返回剩余秒数
注意:
进程挂起指定的秒数后程序并不会立即执行,系统只是将此进1程切换到就绪态
案例:
执行结果:
#include
#include
pid_t wait(int *status);
功能:等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。
调用wait函数的进程会挂起,直到它的一个子进程退出或收到一个不能被忽视的信号时才被唤醒。若调用进程没有子进程或它的子进程已经结束,该函数立即返回。
参数:
status:函数返回时,参数status中包含子进程退出时的状态信息。
子进程的退出信息在一个int中包含了多个字段,
用宏定义可以取出其中的每个字段
子进程可以通过exit或者_exit函数发送退出状态
返回值:
成功:子进程号
失败:-1
WIFEXITED(status)
如果子进程是正常终止的,取出的字段值非零。
WEXITSTATUS(status)
返回子进程的退出状态,退出状态保存在status变量的8~16位。
在用此宏前应先用宏WIFEXITED判断子进程是否正常退出,正常退出才可以使用此 宏。
注意:
此status是个wait的参数指向的整型变量
案例:
执行结果:
#include
#include
pid_t waitpid(pid_t pid, int *status,int options)
功能:等待子进程终止,如果子进程终止了,此函数会回收子进程的资源
案例:
结果:
#include
void _exit(int status);
功能:退出当前进程
参数:
status:退出状态,由父进程通过wait函数接收这个状态
一般失败退出设置为非0
一般成功退出设置为0
返回值:无
#include
void exit(int status);
功能:退出当前进程
参数:
status:退出状态,由父进程通过wait函数接收这个状态
一般失败退出设置为非0
一般成功退出设置为0
返回值: 无
exit和_exit函数的区别:
exit为库函数,而_exit为系统调用函数
exit会刷新缓冲区,_exit不会,一般用exit
案例:
结果:
1 #include
int atexit(void (*function)(void));
功能:注册进程正常结束前调用的函数,进程退出执行注册函数
参数:
function:进程结束前,调用函数的入口地址。
一个进程中可以多次调用atexit函数注册清理函数, 正常结束前调用函数的顺序和注册时的顺序相反
返回值:
成功:0
失败:非0
案例:
执行结果:
#include
#include
pid_t vfork(void);
功能:vfork函数和fork函数一样都是在已有的进程中创建一个新的进程,但它们创建的子进程是有区别的。
参数:无
返回值:
成功:子进程中返回0,父进程中返回子进程ID
失败:‐1。
fork与vfork的区别:
vfork保证子进程先运行,在它调用exec或exit之后,父进程才可能被调度运行。
vfork和fork一样都创建一个子进程,但它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就不访问该地址空间。
相反,在子进程中调用exec或exit之前,它在父进程的地址空间中运行,在exec之后子进程会有自己的进程空间。
结果:
结果:
进程的替换
exec函数族,是由六个exec函数组成的。
1、exec函数族提供了六种在进程中启动另一个程序的方法。
2、exec函数族可以根据指定的文件名或目录名找到可执行文件。
3、调用exec函数的进程并不创建新的进程,故调用exec前后,进程的进程号并不会改变,其执行的程序完全由新的程序替换,而新程序则从其main函数开始执行。
exec函数族取代调用进程的数据段、代码段和堆栈段
一个进程调用exec后,除了进程ID,进程还保留了下列特征不变:
父进程号 、进程组号 、控制终端 、根目录 、当前工作目录 、进程信号屏蔽集 、未处理信号...
案例:
#include
int system(const char *command);
功能:执行一个shell命令(shell命令、可执行文件、shell脚本)
system会调用fork函数产生子进程,子进程调用exec启动/bin/sh ‐c string来执行参数string字符串所代表的命令,此命令执行完后返回原调用进程
参数:
command:要执行的命令的字符串
返回值:
如果command为NULL,则system()函数返回非0,一般为1。
如果system()在调用/bin/sh时失败则返回127,其它失败原因返回‐1
案例:
执行结果: