一.基本概念
1.进程与程序
程序: 是存储在磁盘上的文件,它是包含要执行的机器指令和数据的静态实体
进程: 是一个正在运行的程序,一个程序可能包含多个进程(多任务,多进程),进程在操作系统中是一个执行特殊任务的一个单元。
2.进程的分类
交互进程:需要用户输入数据,也会显示一些结果给用户看
批处理进程:用来执行脚本的进程,例如Makefile.
守护进程:它是一种一直活跃的进程,一般是后台程序,多数情况下是由操作系统启动时候通过开机脚本加载或者由超级用户加载
3.查看进程
ps :显示当前用户当前终端所控制的进程
-a :显示所有用户的进程
-x :包括无终端控制的进程
-u :显示详细信息
-w :以更宽的方式显示
4.进程信息表
USER:属主
PID:进程号
%CPU:CPU占有率
%MEM:内存使用率
VSZ:虚拟内存大小(硬盘模拟的那块)
RSS:物理内存的使用量
TTY:终端设备号,如果不是终端控制进程,用?号表示
STAT:终端的状态
START:开始时间
TIME:运行时间
COMMAND:开启子进程的命令
5.进程的状态:
O:就绪态,一切准备工作都已经做好,等待被CPU调用
R:运行态,Linux没有就绪态,它把就绪态归结为运行态
S:可唤醒的睡眠态,系统调用,获取到资源,收到信息都可以被唤醒。
s:有子进程
+:后台进程组
D:不可被唤醒的睡眠态,必须等到事件发送。
T:暂停态,收到了SIGSTOP信号,当收到SIGCONT信号则继续运行。
X:死亡态
Z:僵尸态
<:高优先级。
N:低优先级。
L:多线程的进程
6.父子进程
如果进程B是由进程A开启的,则那么我把进程A叫做进程B的父进程,进程B叫做进程A的子进程
当子进程结束后,会向父进程发送一个信号,SIGCHLD,父进程收到这个信号后再回收子进程的资源。
当父进程先结束,子进程就会变成孤儿进程,会被孤儿院(init)收养。
如果子进程已经结束,父进程没有及时回收,子进程就变成僵尸进程。
7.进程标识符
1.俗称进程号,它是一个无符号整数(如果一层层进去看可以发现最底层其实是int),使用getpid函数可以获取到
2.这种编号是循环使用的,当进程结束后,它就和这个编号没有关系了,这个号码也会被再次使用
3.延时重用。
二. getxxid
pid_t getpid(void); //获取进程ID
pid_t getppid(void);//获取父进程ID
uid_t getuid(void);//获取实际的用户ID(程序执行者ID)
uid_t geteuid(void);//获取有效的用户ID (程序拥有者ID)
gid_t getgid(void);//获取组ID(程序执行者组ID)
gid_t getegid(void);//获取有效的组ID(程序拥有者组ID)
1.切换用户
2.chmod u+s a.out
3.chmod g+s a.out
打上当前用户组烙印
进程在运行时候会根据实际的调用来获取它的权限,这样可以让程序比实际的拥有者还要高的权限。(认干爹)
三.fork
pid_t fork(void);
功能:创建一个子进程
1.失败返回-1,成功返回两次
2.父进程会返回子进程的ID,子进程返回0.
3.根据返回值的不同,分别为子进程和父进程设计不同的分支
4.通过fork创建出的子进程,就是父进程的副本,会把父进程的数据段,堆,栈,数据取区,全局段,静态数据段,IO流的缓冲区统统进行拷贝,父子代码共享代码段(是需要通信的)。
5.fork()函数调用成功后,父子进程就开始各自执行了,它们的先后顺序是不确定的,但是可以通过某些实现来保证先后顺序
(ps,通过sleep()类似的函数可以实现让某一个进程先执行,某一些后执行)
练习1:编写一个程序,让此程序拥有五个进程同时执行
练习2:编写一个程序,让此程序有三级子进程
6.当总进程数超过系统的限制时,就无法在创建进程了,此时fork函数会返回-1执行失败。
7.孤儿进程:子进程还在运行但父进程已经结束,此时子进程会被孤儿院init(1)收养,子进程变成孤儿进程。
8.僵尸进程:子进程已死亡,但父进程没有及时回收子进程,此时子进程就会检查僵尸进程。
注意:fork()之前的代码,只有父进程在运行,fork()之后的代码,父子进程都有机会执行,根据fork()的返回值来控制进入不同的分支。
9.父进程打开的文件和子进程是共享的,子进程打开的文件暂时无法共享(可以通过进程通信)
四.vfork
pid_t vfork(void);
功能:创建子进程
1.vfork不能单独创建子进程,需要与excl函数簇,配合才能完成子进程的创建。
2.不会复制父进程的栈,堆,数据,全局,等段,也不会共享代码段。而是通过excl函数调用另一个程序直接启动,从而提高创建进程的效率。
3.使用vfork创建子进程保证,先执行子进程,然后执行父进程
fork和vfork的区别
1、fork特点:
fork创建进程时,子进程完全复制父进程的资源,子进程独立于进程,具有良好的并发性,二者之间需要专门的通信机制。
如果某进程fork出一个子进程只是为了调用exec执行另一个文件,那fork过程对于虚拟地址空间的复制是多余的过程。
fork优点:父子进程相互独立,子进程对父进程中同名变量进行修改并不会影响其在父进程中的值。
2、vfork特点:
vfork创建的子进程与父进程共享地址空间,即子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间的修改同样为父进程所见,用vfork创建子进程后,父进程会被阻塞到子进程调用exec或exit。
vfork避免了(fork函数子进程被创建后,仅仅为调用exec执行另一个程序,它对地址空间的复制是多余的)这个问题,减少了不必要的开销。
vfork保证子进程先运行,它调用exec或exit后父进程才能调度运行,fork的父子进程运行顺序不定,取决于内核的调度算法。
父进程中的数据空间和堆、栈可能会产生副本,具体情况要看使用的是fork还是vfork,fork产生副本,vfork则共享这部分内存。
五.进程的正常退出
1.从main函数退出,早main函数中return 0;
返回值的低八位会被父进程获取到
2.
#include <unistd.h>
_exit(state)(系统退出函数)
#include <stdlib.h>
_Exit(state)(标准退出函数),这两个函数几乎没有什么区别。
a.返回值的低八位会被父进程获取到
b.使用_exit/_Exit退出前会关闭所有打开的文件流,如果有子进程则会托付给init,然后向它的父进程发送SIGCHLD信号
c.此函数不会返回
3.调用标准C的exit(stat)函数
a.底层实现上调用了_exit/_Exit函数,所以这两个函数具有的特点他都具备。
b.exit结束前会调用atexit/on_exit注册的函数
int atexit(void (*function)(void));
int on_exit(void (*function)(int , void *), void *arg);
4.最后一个线程正常结束
六.进程异常中止(获取不到返回值)
1.进程调用了abort函数(段错误,浮点异常)
2.进程接收到某些信号
ctrl+c
ctrl+
ctrl+z
3.最后一个线程收到取消操作,而且线程还做出响应了
七.子进程的回收
pid_t wait(int *status);
功能:等待子进程结束,并回收
pid_t waitpid(pid_t pid, int *status, int options);
功能:等待指定的子进程结束并回收
1.当一个进程结束时,内核会向它的父进程发送一个信号
父进程收到这个信号后,可以指定一个函数来处理,也可以选择忽略,默认情况下是忽略的。
2.父进程调用wait/waitpid才是有意义的。
如果所有的子进程都在运行,则父进程阻塞
只要有一个子进程结束了,会立即返回这个进程的id,结束状态
当所有子进程都结束运行时,wait会返回-1
如果在调用wait之间子进程就已经结束(僵尸子进程),执行wait函数会立即返回并回收僵尸进程。
3.waitpid函数可以指定等待那个子进程,
pid 指定的pid
< -1 进程组ID是pid绝对值的进程结束
== -1 和wait一样
== 0 等于进程组ID相同的任意进程
> 0 进程号是pid的进程结束
status用于接收子进程的结束状态,如果不需要状态码,可以设置为空
opention:
0 以阻塞状态等待子进程结束
WNOHANG 如果没有子进程退出,会立即返回
WUNTRACED 等待的进程处于停止状态,并且之前没有报告过,则立即返回返回。
4.如果不调用wait/waitpid函数,子进程结束后就处于僵尸状态,当父进程也结束时,父进程的父进程会统一回收它们。
八.exec函数簇
exec函数的功能:加载一个可执行文件,要和vfork函数配合才有意义
int execl(const char *path, const char *arg, ...);
path:可执行文件的路径
arg:给可执行文件的参数类似于命令行参数,必须以NULL结尾,第一个必须是可执行文件名。
例子:execl("","a.out",NULL);
1.通过exec创建的子进程会替换掉父进程给的代码段,也不拷贝父进程的堆,栈,全局,静态数据段,会用新的可执行文件替换掉它们
2.exec只是加载一个可执行文件,并不创建进程,不会产生新的进程号。
3.只有exec函数执行结束(无论成功还是失败)父进程才能成功进行
int execlp(const char *file, const char *arg, ...);
file:可执行文件的文件名,会从PATH环境变量指定的位置去找文件
int execle(const char *path, const char *arg,
..., char * const envp[]);
添加一个可指定的环境变量表
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[]);
这三个函数则是给如一个字符串数组,每个元素为字符串首地址