进程基本概念:
1、进程与程序
程序就是存储在磁盘上的可执行文件,程序被加载到内存中开始运行叫进程。一个程序可以被多次加载生成多个进程,进程就是处于活动状态的计算机程序。
2、进程的分类
进程一般分为三种类型:交互进程、批处理进程、守护进程。
守护进程一般都处于活跃状态,运行在后台,由于系统在开机时通过启动脚本自动创建的。
3、查看进程
简单形式:ps 显示当前用户有控制终端的进程信息。
列表形式:ps auxw 显示进程详细信息
a 所有用户的有控制终端的进程
x 无终端控制的进程
u 显示进程的详细信息
w 以更大列宽显示
USER 进程的属主
PID 进程的编号
%CPU CPU使用率
%MEM 内核使用率
VSZ 虚拟内存使用的字节数
RSS 物理内存使用的字节数
TTY 终端设备号,?表示无终端控制
STAT 进程的状态
O 就绪,等待被调度
R 运行,Linux系统没有O,就绪也用R表示
S 可被唤醒的睡眠,如系统中断、获得资源、收到信号都可以唤醒它转入运行状态。
D 不可被唤醒的睡眠,只能被系统唤醒。
T 暂停状态,收到SIGSTOP信号进程暂停状态,收到SIGCONT信号转入运行状态。
W 等待内存分页(2.6内核以后被废弃)
X 死亡状态
Z 僵尸状态
< 高优级
N 低优先级
l 多线程的进程
s 会话首进程
L 有内存被锁进内存分页
+ 在前台进程级中
START 进程启动的时间
TIME 进程运行的时间
COMMAND 启动进程的命令
4、父进程、子进程、孤儿进程、僵尸进程
一个进程可以被另一个进程创建,创建者叫父进程,被创建者叫子进程,子进程被父进程启动后在操作系统的调用同时运行。
当子进程先于父进程结束,子进程会向父进程发送SIGCHLD信号,此时父进程应该去回收子进程的相关资源,如果没有收回子进程就会进入僵尸状态。
僵尸进程:该进程已经死亡,但它的父进程没有立即回收它的相关资源,该进程就进入僵尸状态。
孤儿进程:父进程先于进程结束,子进程就变成了孤儿进程,孤儿进程会被孤儿院(init守护进程)领养,init就是孤儿进程的父进程。
5、进程标识符
每个进程都有一个以非负整数表示的唯一标识,即进程ID/PID。
进程ID在任意时刻都是唯一的,但可以重用,进程一旦结束它的进程ID就会被回收,过一段时间后再重新分配给其它新创建的进程(延时重用)。
pid_t getpid(void);
功能:获取进程ID
pid_t getppid(void);
功能:获取父进程ID
init的进程ID永远是1。
创建进程:
int system(const char *command);
功能:执行一个可以执行文件,这样就创建一个子进程。
返回值:子进程结束后才返回。
该函数的实现调用了fork和waitpid函数,其实是当进程创建一个子进程,子进程又加载了command可执行文件。
pid_t fork(void);
返回值:一次调用两次返回,子进程返回0,父进程返回子进程ID,当进程的数量超过系统的限制会创建进程失败,返回-1。
可以根据返回值的不同让父子进程进入不同的分支,执行不同的代码。
该函数调用后父子进程各自独立运行,谁先返回不确定,但可以通过睡眠确定让哪个进程先执行。
通过fork创建的子进程会拷贝父进程的(数据段、bss段、堆、栈、I/O流缓冲区)等数据同,与父进程共享代码段,子进程会继承父进程的信号处理方式。
通过fork创建的子进程可以共享父进程的文件描述符。
pid_t vfork(void);
功能:以加载可以执行文件方式创建子进程。
返回值:子进程返回0,父进程返回子进程的PID。
子进程先返回,此时子进程并没有创建成功,需要加载一个可以执行文件来替换当前子进程的所有资源,替换完成子进程才算创建成功,此时父进程才返回。
使用exec系列函数加载可执行文件:
int execl(const char *path, const char *arg, ...);
path:可执行文件的路径,
arg:命令行参数,一般每个是可执行文件的名字,至少一个,NULL结尾。
int execlp(const char *file, const char *arg, ...);
file:可执行文件名字,会根据PATH环境变量的查找可执行文件;
arg:命令行参数
int execle(const char *path, const char *arg, ..., char * const envp[]);
path:可执行文件的路径
arg:命令行参数
envp:环境变量表,父进程可以在加载子进程时把环境变量传递给子进程,这样父子进程共用一个环境变量表。
int execv(const char *path, char *const argv[]);
path:可执行文件的路径
argv:指针数组,由传递给子进程的字符串组成,以NULL结尾
int execvp(const char *file, char *const argv[]);
file:可执行文件名字,会根据PATH环境变量的查找可执行文件。
argv:指针数组
int execvpe(const char *file, char *const argv[],char *const envp[]);
file:可执行文件名字
argv:指针数组
envp:环境变量表
exec系列函数正常是不会返回会,当加载子可执行文件失败时返回-1。
以excl系列函数创建出的子进程不会继承父进程的信号处理函数,但能继承父进程的信号屏蔽。
进程的正常退出:
1、在main函数中执行 return n,该返回值可以被父进程接收到,它与exit几乎等价。
2、进程调用了exit函数,该函数是标准库函数:
void exit(int status);
功能:在任何时候调用此函数都可以结束进程。
status:结束状态码(EXIT_SUCCESS/EXIT_FAILURE),与main函数中return的返回值效果一样,
返回值:该函数不会返回
进程退出前要完成:
int atexit(void (*function)(void));
功能:注册一个进程结束时执行的函数
int on_exit(void (*function)(int , void *), void *arg);
功能:注册一个进程结束时执行的函数
int: return 的值或exit函数的参数
arg:会在进行结束时自动传递给function函数。
1、先调用事先通过at_exit/on_exit注册过的函数,如果都注册了执行顺序与注册顺序相反。
2、冲刷并关闭所有打开状态的标准IO流。
3、该函数的实现调用了_Exit/_exit。
3、调用_exit/_Exit函数
void _exit(int status);
功能:结束进程,由系统提供
void _Exit(int status);
功能:结束进程,由标准库提供
1、它们的参数会被父进程获取到。
2、进程结束前会关闭所有处理打开状态的文件描述符。
3、会向父进程发送SIGCHLD.
4、该函数不会返回。
4、进程的最后一个线程执行的返回语句。
5、进程的最后一个结束调用pthread_exit函数。
进程的异常终止:
1、进程调用了abort函数,产生了SIGABRT信号(自杀)。
2、进程接收到了某些信号,可以是其它进程发送的,也可能自己的错误操作造成的(他杀,作死)。
3、进程的最一个线程收到了"取消"操作,并作出了响应。
这三种结束方式父进程无法猎取结束状态码,所以叫异常终止。
注意:不管进程是如何结束的它们都会执行同一段代码,关闭所有打开的文件描述符,释放所有的内存。
子进程回收:
对于任何结束方式,都希望父进程能够知道,通过wait、waitpid函数可以知道子进程是如何结束的以及结束状态码。
pid_t wait(int *status);
功能:等待子进程结束,并获取结束状态。
返回值:子进程的ID
1、如果所有子进程都在运行,则阻塞。
2、如果有一个子进程结束,立即返回该进程结束状态和PID。
3、如果没有子进程则返回-1。
WIFEXITED(status) 判断进程是否是正常结束,如果是则返回真。
WEXITSTATUS(status) 如果进程是正常结束的,可以获取到正确的结束状态码,只获取低8位。
WIFSIGNALED(status) 判断进程是否是异常结束,如果是则返回值。
WTERMSIG(status) 如果进程是异常结束的,可以获取到杀死进程的信号。
注意:由于wait函数可能会阻塞,因此不适合在业务逻辑中调用些函数,可以为SIGCHLD信号注册一个处理函数,在处理函数中调用wait函数。
pid_t waitpid(pid_t pid, int *status, int options);
功能:指定收回某个或某些进程
pid:
<-1 等待abs(pid) 进程组中的进程结束。
-1 等待任意子进程结束,功能与wiat等价。
0 等待同组的任意进程结束。
>0 等待该进程结束。
status:结束状态,与wiat中的等价。
options:
WNOHANG 非阻塞模式,如果没有进程结束,立即返回0。
WUNTRACED 如果有进程处理暂停状态,返回该进程的状态。
WCONTINUED 如果有进程由暂停转为继续运行,返回该进程的状态。
WIFSTOPPED(status) 判断进程是否处理暂停状态,是则返回真。
WSTOPSIG(status) 获取到导致该进程暂停的信号。
WIFCONTINUED(status) 判断该进程是否由暂停转为继续运行,是则返回真。