基本概念
1、进程与程序
程序:存储在磁盘上的文件,包含可执行指令和数据的静态实体。
进程:运行中的程序(一个程序可以执行多次,加载出多个进程)。
进程就是处于活动状态的计算机程序。
2、进程的分类:
交互进程:有输入输出,用户可以根据自己的情况输入数据,得到想要的结果(一般进程)。
批处理进程:由脚本加载执行的程序(比如Linux下shell,Windows下bat文件)。
守护进程:总是活跃的、后台运行,一般由系统开机时加载执行或root用户手动加载执行。
3、查看进程
简单方式:ps 显示出当前用户有终端控制权的进程信息。
列表形式:ps aux 以列表形式显示详细信息
a 所有用户
u 以详细方式显示
x 所有无终端控制的进程
4、进程的详细信息列表
USER 进程的用户名
PID 进程id
%cpu cpu使用率
%MEM 内存使用率
VSZ 占用虚拟内存的大小
RSS 占用物理内存大小
TTY 有终端显示终端的次设备号,如果无终端控制权显示“?”
STAT 进程的状态:
0 就绪态,等待被系统调用
R 运行态,Linux下没有就绪态,就绪态用R表示
S 休眠态,可以被系统中断(信号)唤醒转入运行态
T 暂停态,是被SIGSTOP信号暂停,当收到SIGCONT信号时,才能再转入运行态
Z 僵尸态,已经结束停止运行,但父进程还没有回收
< 高优先级进程
N 低优先级进程
l 多线程化的进程
+ 在前台进程组中的进程
s 会话的首进程
START TIME 进程的开始时间
COMMAND 进程的可执行文件名
5、父进程与子进程,孤儿进程与僵尸进程
一个进程A可以创建出另一个进程B,创建者叫父进程,被创建者叫子进程,父进程启动子进程后,在操作系统的调用下父子进程同时执行(同步)。
如果子进程先于父进程结束,会向父进程发送一个信号SIGCHLD,父进程收到信号后,就应该去回收子进程的相关资源,但在默认情况下,父进程忽略该信号。
也就是说,当子进程结束后,父进程没有回收子进程的资源,那么子进程就变成了僵尸进程。
如果父进程先于子进程结束,子进程变成了孤儿进程,同时被孤儿院(init)收养,然后变成了init的子进程。
进程标识符
操作系统会为每个进程分配一个唯一的标识符,而且采用无符号整数表示,即为进程ID。
进程ID在任何时候都是唯一的,但是可以重用,当一个进程结束,新创建的进程才可以使用它的进程ID(延时重用)。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
功能:获取进程ID
pid_t getppid(void);
功能:获取父进程ID
uid_t getuid(void);
功能:获取当前进程用户ID
gid_t getgid(void);
功能:获取当前进程的组ID
创建进程
#include <unistd.h>
pid_t fork(void);
功能:创建一个新进程
返回值:一次调用两次返回,失败返回-1(当进程数超出系统的限制,进程创建结果失败)
注意:
1、两次返回分别是进程ID(子进程)和0,父进程会拿到子进程的ID,子进程返回0,借此可以分别出父子进程,编写不同的处理分支。
2、通过fork创建子进程就是父进程的副本(拷贝)。子进程会获取父进程的数据段、BSS段、堆、栈、IO流(共享文件指针和文件描述符)、缓冲区的拷贝,与父进程共享代码段。
3、子进程会继承父进程的信号处理方式。
4、fork函数调用后,父子进程各自执行,谁先返回不一定,但可以使用一些手法来确保谁先执行
5、僵尸进程与孤儿进程的实现。
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
功能:与fork功能基本一致
区别:通过vfork创建的进程不复制父进程的地址空间(数据段、BSS段、堆、栈、IO流、缓冲区),必须通过execl系列函数加载自己的可执行程序。
注意:当执行vfork时,子进程先返回,此时它占用了父进程的地址空间,当子进程成功创建后,通过execl加载可执行程序,父进程才返回。
execl系列函数
功能:加载子进程的可执行文件。
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
path:可执行文件路径
arg:第一个main函数参数,最后一个必须时以NULL结尾。
int execlp(const char *file, const char *arg, ...);
file:可执行文件的文件名,会从PATH环境变量的路径中查找可执行文件并执行
argc:第一个main函数的参数,最后一次必须以NULL结尾。
int execle(const char *path, const char *arg,..., char * const envp[]);
path:可执行文件路径
argc:第一个main函数的参数,最后一次必须以NULL结尾。
exvp:父进程的环境变量表,传递给子进程
int execv(const char *path, char *const 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函数
#include <stdlib.h>
void exit(int status);
功能:调用者立即结束该进程
status:退出状态码,可以在父进程中获取到,子进程留给父进程的遗言。
退出前做的事情:
1、先调用事先注册的函数(通过atexit/on_exit)。
#include <stdlib.h>
int on_exit(void (*function)(int , void *), void *arg);
功能:注册一个函数,当进程通过exit函数开始结束时调用。
function:函数指针,无返回值,参数1为exit函数的参数,参数2为on_exit的第二个参数。
arg:当function函数被调用时传递给它第二个参数
int atexit(void (*function)(void));
功能:注册一个函数,当进程通过exit函数结束时调用。
function:函数指针,无返回值无参数
成功返回0失败返回-1
这两个注册函数都会加入同一个栈中,谁最后等级的谁先执行,它们的区别只有注册的函数格式不同,最多可以登记32个函数。
2、冲刷所有处在未关闭状态的标准IO流(fflush)。
3、返回一个整数(EXIT_SUCCESS/EXIT_FAILURE)给操作系统。
4、该函数不会返回,它的功能实现借助了_exit/_Exit
3、_exit/_Exit函数退出
注意:这两个函数的功能是一样的。
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);//调用系统的_exit
功能:调用的进程会结束,没有返回值。
status:会被父进程获取到(低八位,1个字节)
1)进程结束前会关闭所有处于打开状态的文件描述符。
2)把所有子进程托付给孤儿院init
3)向它的父进程发送SIGCHLD信号
注意:exit函数也会执行以上操作,因为它底层调用了_exit/_Exit。
4、进程的最后一个线程执行了最后一条语句。
5、进程的最后一个线程调用了pthread_exit函数。
进程的异常退出
1、调用了abort函数,该函数会产生SIGABRT信号。
2、进程接收到一些信号(无法捕获处理)。
3、进程的最后一个线程收到“取消”请求,并做出响应,相当于线程收到了结束“信号”。
进程结束
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:等待所有子进程结束,并获取到最终的状态码,只要有一个进程结束就立即返回。
返回值:现在回收的子进程id
1、应该是父进程收到子进程发送来的SIGCHLD信号时,调用wait函数回收子进程的资源并获取结束状态。
2、如果所有子进程都在运行,则wait阻塞。
3、如果已有僵尸进程,wait也会立即返回,回收资源获取结束状态码。
4、如果没有子进程则返回失败-1。
pid_t waitpid(pid_t pid, int *status, int options);
功能:等待指定的进程结束,并获取到最终的状态码。
pid:
=-1 等待任意子进程的结束,此时与wait等价
>0 等待进程号为pid的进程结束,此时只等待一个进程结束
=0 等待同组的任意子进程结束,此时等待的是整个进程组
<-1 等待的是进程组id是pid绝对值中的任意子进程结束,此时等待的是整个进程组
options:
WNOHANG 非阻塞模式,如果没有子进程结束则立即退出。
WUNTRACED 如果子进程处于暂停状态,则返回它的状态。
WCONTINUED 如果子进程从暂停后转为继续,则返回它的状态。
1、wait函数只能孤独的等待子进程结束,而waitpid可以有更多的选择。
2、waitpid不光可以等待子进程也可以等待同组进程
3、waitpid可以阻塞也可以不阻塞。
4、也可以监控子进程的暂停或继续状态。
system函数
#include <stdlib.h>
int system(const char *command);
功能:执行系统命令的,也可以加载可执行程序。
相当于创建了一个子进程,但子进程不结束,该函数不返回,父子进程不会同时进行。
该函数的实现应该调用了:vfork、exec、wait等函数。
进程组
进程组是由一个或多个进程的集合,每个进程除有一个进程ID还有一个进程组ID,进程组中的进程归属同一个作业控制(负责完成同一个任务)。
同一进程组的进程,会统一接收到终端的信号,由fork创建的子进程,默认就加入了父进程的进程组。
每个进程组都有一个组长,组长的进程ID就是组ID
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
功能:获取某个pid进程的进程组ID
pid_t getpgid(pid_t pid);
功能:设置进程pid进程的进程组ID,就相当于加入pgid进程组。pgid就是它的组长