进程基本概念
进程与程序
程序:就是存储在磁盘上的文件,里面包含了一些可以执行的二进制指令和数据。
进程:就是运行着的程序(处于活动状态的程序)。
查看进程
1、简单形式:ps 以简略形式显示当前用户控制的终端下的进程。
2、以列表形式显示进程的详细信息:ps aux
-a 所以用户控制的终端进程
-u 以详细形式显示
-x 包括无终端控制的进程
3、进程详细信息表
USER:进程属主
PID:进程ID
%CPU:CPU占用率
%MEM:内存使用率
VSZ:占用虚拟内存大小
RSS:占用物理内存大小
TTY:终端设备号,?表示无终端控制的进程
STAT:进程的状态
O 就绪状态,等待被调用
R 运行状态,Linux系统没有O状态,就绪态也就是运行态。
S 可被唤醒的睡眠状态,当系统中断、获得到资源、收到信号都可以反它唤醒然后转入运行状态。
D 不唤醒的睡眠状态,只能被wake_up系统调用唤醒。
T 暂停,收SIGSTOP(19)信号
X 死亡状态
Z 僵尸,已经停止,但父进程没有回收它的相关资源。
s 会话首进程
< 高优先级
N 低优先级
l 多线程化的进程
+ 在前台进程组中
START:进程启动时间
TIME:进程运行了多长时间
COMMAND:启动进程的指令(可执行文件的名字)
父进程、子进程、孤儿进程、僵尸进程
一个进程可以创建另一个进程,创建者叫父进程,被创建者叫子进程。
当一进程的父进程先于子进程结束,子进程就变成了孤儿进程,孤儿进程会被收养到孤儿院(init)。有些版本的孤儿进程不会被收到孤儿院。
当一个进程死亡时,它的父进程没有回收相关资源,此进程就变成了僵尸进程。
进程标识符
每一个进程都有一个以非负整数表示的唯一编号,即进程PID。
进程ID在任何时候都是唯一的,当可以重用,当进程结束时它的ID可以给新的进程用,采用的规则是延迟重用。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
功能:获取当前进程的ID
pid_t getppid(void);
功能:获取父进程的ID
fork
#include <unistd.h>
pid_t fork(void);
功能:创建一个子进程
返回值:失败返回-1,成功返回两次
父进程:返回子进程的pid
子进程:返回0
根据返回值的不同可以分别为父子进程编写不同的处理分支。
1、fork函数调用后父子进程各自运行,先后顺序不确定,但某些操作系统可以保证子进程先运行,父子进程谁先返回不确定。
2、子进程是父副本:子进程被创建时会拷贝父进程的data、bss、heap、stack、缓冲区等,唯独代码段不拷贝,父子进程共享一个代码段。
fork函数调用结束后,父进程的文件描述符,会复制到子进程中,此时子进程中也是可以继续该文件的,因为所有进程共享一个文件表(内核级)。
没有父子关系的两个进程,即使文件描述符相同,也不能共享访问同一个文件。
问题1:子进程是否会继承父进程的信号处理方式,实验证明。
通过fork创建的子进程会继承父进程的信号处理方式。
问题2:子进程结束是是否会向父进程报告,父进程是否会有反应。
子进程结束后内核会向父进程发送SIGCHLD信号,父进程默认的处理该信号方式是忽略,也可以进行捕获后回收子进程的资源。
总进程数是有限的,当进程过多超过系统限制,fork就会失败(不要轻易尝试,当fork失败前系统就已经卡的不行了)。
vfork
pid_t vfork(void);
功能:创建子进程
与fork的区别:
1、子进程先返回,父进程等待,临时,需要子进程真正创建出来,然后父进程才返回。
2、vfork无法单独创建出一个子进程,需要与exec函数配合。
3、vfork不会复制父进程的相应数据内存,而是进行替换。
exec系列函数
这一系列函数的功能就是配合vfork创建子进程。
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[]);
envp:环境变量表,创建子进程时传递父进程的环境变量表。
int execv(const char *path, char *const argv[]);
argv:命令参数以指针数组的形式提供
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
进程的正常退出
1、从main函数中return
2、调用标准库函数exit
1、EXIT_SUCCESS/EXIT_FATLURE,约定好的进程的退出状态码,把参数的低8位返回给父进程。
2、进程退出前会调用通过atexit/on_exit函数注册的函数。
int atexit(void (*function)(void));
功能:注册一个函数,当进程退出时调用此函数。
int on_exit(void (*function)(int , void *), void *arg);
功能:注册一个函数,当进程退出时调用此函数,同时可以附加一个指针参数(如果进程是通过return从main函数退出,则不能指向栈空间,因此此时栈空间可能已经被释放)。
3、冲刷处理打开状态的标准IO流的缓冲区,并关闭文件。
4、exit函数不会有返回,它之后的代码不会被执行。
5、该函数的底层调用了系统的exit_group函数。
3、调用_Exit/_exit函数退出进程
#include <unistd.h>
void _exit(int status); // 系统
status: 0正常,-1不正常
#include <stdlib.h>
void _Exit(int status); // 标准库
功能:结束进程,这两个函数完全等价
6、进程退出前会关闭处于打开状态的文件描述符。
7、把所有的子进程托付给init进程(PID为的进程,也叫孤儿院),这个行为叫托孤。
8、向父进程发送SIGCHLD信号。
4、进程的最后一个线程执行的返回语句。
5、进程的最后一个线程调用了pthread_exit函数。
进程的异常终止
1、进程收到某些信号,他杀。
2、进程自己调用abort函数,产生了SIGABRT信号,自杀。
3、进程的最后一个线程收到"取消"操作,并作出响应。
子进程的回收
pid_t wait(int *status);
功能:以阻塞状态等待子进程结束,并回收它的相关资源,只要有一个子进程结束它就返回,如果当前进程没有子进程就立即返回。
status:获取子进程的退出状态码,但只能获取到低8位。
需要相关的宏进行解析才能获取到。
返回值:成功返回子进程的PID,失败返回-1。
pid_t waitpid(pid_t pid, int *status, int options);
功能:以阻塞状态等待子进程结束,与wait不同的是它可以指定等待哪个进程。
pid:要等待的子进程PID
<-1 等待进程组中的任意子进程结束
=-1 等待任意子进程结束,此时功能与wait等价。
=0 等待同组的任意进程结束。
>0 此时它代表着一个特定的子进程。
status:获取子进程的结束状态码, 需要相关的宏进行解析才能获取到。
options:
0 表示忽略此参数,功能与wait一样。
WNOHANG 如果没有子进程结束立即返回。
WUNTRACED 如果有子进程暂停也立即返回
WCONTINUED 如果有子进程从暂停状态切换为继续状态,立即返回。
返回值:成功返回子进程的PID,失败返回-1。