1、进程简述
进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建、调度和消亡的过程,是Linux 的基本调度单位。通过进程控制块( PCB)来描述的。进程控制块包含了进程的描述信息、控制信息以及资源信息,它是 进程的一个静态描述。
-
进程标识
-
- OS 会为每个进程分配一个唯一的整型 ID, 做为进程的标识号(pid)。 进程除了自身的 ID 外, 还有父进程 ID(ppid),所有进程的祖先进程是同一个进程, 它叫做 init 进程, ID 为 1, init 进程是内核自举后的一个启动的进程。 init 进程负责引导系统、 启动守护( 后台) 进程并且运行必要的程序。
-
进程的 pid 和 ppid 可以分别通过函数 getpid()和 getppid()获得
-
- OS 会为每个进程分配一个唯一的整型 ID, 做为进程的标识号(pid)。 进程除了自身的 ID 外, 还有父进程 ID(ppid),所有进程的祖先进程是同一个进程, 它叫做 init 进程, ID 为 1, init 进程是内核自举后的一个启动的进程。 init 进程负责引导系统、 启动守护( 后台) 进程并且运行必要的程序。
-
进程的用户 ID 与组 ID(进程的运行身份)
-
- 进程在运行过程中, 必须具有一类似于用户的身份, 以便进行进程的权限控制, 缺省情况下, 哪个登录用户运行程序, 该程序进程就具有该用户的身份。
-
真实用户 ID 和真实组 ID 可以通过函数getuid()和 getgid()获得。
-
与真实 ID 对应, 进程还具有有效用户 ID 和有效组 ID 的属性, 内核对进程的访问权限检查时, 它检查的是进程的有效用户 ID 和有效组 ID, 而不是真实用户 ID 和真实组 ID。
-
有效用户 id 和有效组 id 通过函数geteuid()和 getegid()获得。
-
- 进程在运行过程中, 必须具有一类似于用户的身份, 以便进行进程的权限控制, 缺省情况下, 哪个登录用户运行程序, 该程序进程就具有该用户的身份。
-
s 权限提升 ,eg: a.out的可执行程序的文件所有者是 gotter,当执行
chmod u+s a.out (注意最后一个参数必须是可执行程序)后,此时已经提升权限
之后, 我们切换用户ghaha,并运行程序 a.out,此时,进程的有效用户身份变为了
ghaha,而不是 gotter 了, 这是因为文件 a.out 的访问权限 的所有者可执行为设置
了 s 的属性, 设置了该属性以后, 用户运行 a.out 时,a.out 进程的有效用户 身份将
不再是运行 a.out 的用户, 而是 a.out 文件的所有者。
-
-
修改密码就是最典型的应用例子。
-
-
粘滞位权限 T ------只用于目录,当用户删除时,检查的是目录真实的拥有者,而不是写权限。
-
S 是 chmod u-x a.out 之后的显示效果,
-
t 是 chmod o+t dir1, 可用于多人合作共享,只能删除自己的文件夹,但可以查看别人的文件夹。
-
T 是 chmod o-x dir1 之后的效果,变成T后,其他用户就不能进来了
2、进程的状态
1、执行态: 该进程正在运行, 即进程正在占用 CPU。
2、就绪态: 进程已经具备执行的一切条件, 正在等待分配 CPU 的处理时间片。
3、等待态: 进程不能使用 CPU, 若等待事件发生( 等待的资源分配到) 则可将其唤醒。
3、Linux 下的进程结构
Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。也就是说,进程之 间是分离的任务,拥有各自的权利和责任。其中,每个进程都运行在各自独立的虚拟地址空间,因此, 即使一个进程发生了异常,它也不会影响到系统的其他进程。
- Linux 中的进程包含 3 个段, 分别为“数据段”、 “代码段”和“堆栈段”。
-
- “数据段”放全局变量、 常数以及动态数据分配的数据空间。 数据段分成普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量) 、BSS 数据段(存放未初始化的全局变量) 以及堆( 存放动态分配的数据) 。
- “代码段”存放的是程序代码的数据。
- “堆栈段”存放的是子程序的返回地址、 子程序的参数以及程序的局部变量等。
- “数据段”放全局变量、 常数以及动态数据分配的数据空间。 数据段分成普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量) 、BSS 数据段(存放未初始化的全局变量) 以及堆( 存放动态分配的数据) 。
4、Linux 下的进程管理
1、与进程管理相关的命令
注:
- 进程 process: 是os的最小单元os会为每个进程分配大小为4g的虚拟内存空间, 其中 1g 给内核空间3g给用户空间{ 代码区 数据区 堆栈区}
- ps –elf 查看所有的进程 ps –elLf 查看线程
- ps -elf | grep 'aa'查找指定(aa) 进程
- ps -aux 看%cpu(cpu 使用量) %mem( 内存使用量) stat 状态{ S 睡眠 T 暂停 R 运行 Z 僵尸}
- [bg 作业 ID]可以将该进程带入后台运行
- 利用 jobs 可以查看后台任务
- fg 1 把后台任务带到前台, 这里的 1 表示作业 ID
- kill -9 进程号表示向某个进程发送 9 号信号, 从而杀掉某个进程
- renice 是改变优先级的 进程优先级有140个,从-40到99
- 优先级默认值越低,其优先级越高。
2、进程的创建
Linux 下有四类创建子进程的函数: system(), fork(), exec*(), popen()
-
system 函数 (也可以用来嵌脚本)
-
- #include <stdlib.h>
-
int system(const char *string);
-
system 函数通过调用 shell 程序/bin/sh –c 来执行 string 所指定的命令, 该函数在内部是通过调用execve(“/bin/sh”,..)函数来实现的。 通过 system 创建子进程后, 原进程和子进程各自运行, 相互间关联较少。 如果 system 调用成功, 将返回 0。
-
- #include <stdlib.h>
-
fork 函数 (非常重要)
-
- #include <unistd.h>
-
pid_t fork(void);
-
它从已存在进程中创建一个新进程。 新进程为子进程,而原进程为父进程。 它和其他函数的区别在于: 它执行一次返回两个值。其中父进程的返回值是子进程的进程号,而子进程的返回值为0.若出错则返回-1.因此可以通过返回值来判断是父进程还是子进程。
-
**注意:**使用 fork 函数得到的子进程是父进程的一个复制品, 它从父进程继承了进程的地址空间, 包括进程上下文、 进程堆栈、 内存信息、 打开的文件描述符、 信号控制设定、 进程优先级、 进程组号、 当前工作目录、 根目录、 资源限制、 控制终端,而子进程所独有的只有它的进程号、资源使用和计时器等。 通过这种复制方式创建出子进程后, 原有进程和子进程都从函数 fork 返回,各自继续往下运行, 但是原进程的 fork 返回值与子进程的 fork 返回值不同,在原进程中,fork返回子进程的pid,而在子进程中, fork 返回 0,如果 fork 返回负值, 表示创建子进程失败
-
- #include <unistd.h>
-
exec 函数族
-
- exec*由一组函数组成
-
int execl(const char *path, const char *arg, ...)
-
exec 函数族的工作过程与 fork 完全不同, fork 是在复制一份原进程, 而 exec 函数是用 exec 的第一个参数指定的程序覆盖现有进程空间( 也就是说执行 exec 族函数之后, 它后面的所有代码不在执行)。
-
path 是包括执行文件名的全路径名
-
arg 是可执行文件的命令行参数, 多个用, 分割注意最后一个参数必须为 NULL。
-
- exec*由一组函数组成
-
popen 函数类似于 system 函数, 与 system 的不同之处在于它使用管道工作
-
- #include <stdio.h>
-
FILE *popen(const char *command, const char *type);
-
int pclose(FILE *stream);
-
command 为可执行文件的全路径和执行参数;
-
type 可选参数为”r”或”w”,如果为”w”,则 popen 返回的文件流做为新进程的标准输入流,
-
即 stdin,如果为”r”,则 popen返回的文件流做为新进程的标准输出流。
-
pclose 等待新进程的结束, 而不是杀新进程。
-
- #include <stdio.h>
3、进程的控制
-
如果父进程先于子进程退出, 则子进程成为孤儿进程, 此时将自动被 PID 为 1 的进程( 即 init) 接管。 孤儿进程退出后, 它的清理工作有祖先进程 init 自动处理。
-
如果子进程先退出, 系统不会自动清理掉子进程的环境, 而必须由父进程调用 wait 或waitpid 函数来完成清理工作, 如果父进程不做清理工作, 则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸( zombie) 进程过多, 将会影响系统的性能, 所以必须对僵尸进程进行处理。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait 和 waitpid 都将暂停父进程, 等待一个已经退出的子进程, 并进行清理工作;
wait 函数随机地等待一个已经退出的子进程, 并返回该子进程的 pid;
waitpid 等待指定 pid 的子进程; 如果为-1 表示等待所有子进程。
status 参数是传出参数, 存放子进程的退出状态;
通常用下面的两个宏来获取状态信息:
WIFEXITED(status) 如果子进程正常结束, 它就取一个非 0 值。 传入整型值,非地址
WEXITSTATUS(status) 如果 WIFEXITED 非零, 它返回子进程的退出码
options 用于改变 waitpid 的行为, 其中最常用的是 WNOHANG, 它表示无论子进程是 否退出都将立即返回, 不会将调用者的执行挂起。
eg:
if(WIFEXITED(status))
{
printf("the child exit value=%d\n",WEXITSTATUS(status));
}else{
printf("child crash\n");
}
4、进程的终止
进程的终止有 5 种方式:
- main 函数的自然返回;
- 调用 exit 函数
- 调用_exit 函数
- 调用 abort 函数
- 接收到能导致进程终止的信号 ctrl+c SIGINT ctrl+\ SIGQUIT
前 3 种方式为正常的终止, 后 2 种为非正常终止。 但是无论哪种方式, 进程终止时都
将执行相同的关闭打开的文件, 释放占用的内存等资源。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上运维知识点,真正体系化! 详情docs.qq.com/doc/DSmdCdUNwcEJDTXFK