系列文章目录
文章目录
- 系列文章目录
- 前言
- 一、进程状态查看
- 二、Z(zombie)-僵尸进程
- 1.僵尸进程概念
- 2.僵死状态/进程
- 3.僵死状态/进程处理
- 4.僵尸进程的模拟实现
- 5.僵尸进程的危害
- 6.进程状态总结
- 三、孤儿进程
- 1.孤儿进程概念
- 2.孤儿进程
- 3.孤儿进程的模拟实现
- 4.僵尸进程和孤儿进程会造成什么?
- 四、进程优先级
- 1.进程优先级基本概念
- 2.查看系统进程
- 3.PRI and NI
- 4.PRI vs NI
- 5.查看进程优先级的命令
- 1.用top命令更改已存在进程的nice:
- 6.其他概念
- 五、环境变量
- 1.环境变量基本概念
- 2.为什么需要环境变量
- 3.常见的环境变量
- 4.查看环境变量的方法
- 5.测试PATH
- 6.测试HOME
- 7.和环境变量相关的命令
- 8.环境变量的组织方式
- 9.通过代码如何获取环境变量
- 10.通过代码如何获取环境变量
- 11.环境变量通常是具有全局属性
- 总结
前言
一、进程状态查看
二、Z(zombie)-僵尸进程
1.僵尸进程概念
子进程先于父进程结束,而父进程又没有调用wait或者waitpid获取其退出信息。子进程还需要在其PCB中保存其退出的相关信息,所以,子进程的执行主体已经结束,但是操作系统并没有释放该进程PCB结构,以满足父进程后续对该子进程退出信息的查询(如果父进程还在运行)。在子进程结束运行之后,父进程读取其退出状态之前,我们称该子进程为僵尸进程。
2.僵死状态/进程
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
3.僵死状态/进程处理
由父进程调用wait或者waitpid获取其退出信息,但是调用wait或者waitpid的进程会阻塞运行,导致父进程在子进程结束之前不能执行其他事情。在此基础之上可用三种方式解决
-
重启操作系统(建议不推荐)。
-
利用信号SIGCHLD,子进程状态改变后会向其父进程发送SIGCHLD信号。父进程在接受到该信号后,在信号处理函数中调用wait或者waitpid。
-
将僵尸进程的父进程杀掉,将最终使用的子进程变为孤儿进程,从而交由init进程处理其退出信息。
4.僵尸进程的模拟实现
代码如下:
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid=fork();
if(pid<0)
{
printf("fork error!\n");
}
else if(pid==0)
{
printf("i am child:pid=%d,ppid=%d\n",getpid(),getppid());
//子进程
}
else
{
while(1)
{
printf("i am father:pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
//父进程
}
return 0;
}
子进程的状态:
5.僵尸进程的危害
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护?是的!
- 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
- 内存泄漏?是的!
6.进程状态总结
至此,值得关注的进程状态全部讲解完成,下面来认识另一种进程。
三、孤儿进程
1.孤儿进程概念
父进程结束或者异常终止,但是子进程继续运行。此时子进程的PPID被设置为1,即init进程。init进程接管了该子进程,并等待它结束,在父进程退出之后,子进程退出之前,该子进程属于孤儿进程(在Linux高性能服务器编程中也被称为僵尸进程)。
2.孤儿进程
- 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
- 父进程先退出,子进程就称之为“孤儿进程”
- 孤儿进程被1号init进程领养,当然要有init进程回收喽。
3.孤儿进程的模拟实现
代码如下:
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid=fork();
if(pid<0)
{
printf("fork error!\n");
}
else if(pid==0)
{
//子进程
while(1)
{
printf("i am child:pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
}
else
{
printf("i am father:pid=%d,ppid=%d\n",getpid(),getppid());
}
return 0;
}
由于进程号为17398的进程变成后台进程,所以不能ctrl+c退出。
要使用kill -9 强杀命令杀掉17398进程。**
题目:
4.僵尸进程和孤儿进程会造成什么?
无论哪种情况,如果父进程没有正确的处理子进程的返回信息,子进程都将停留在僵尸态或者孤儿态,并占据着内核资源。这是不允许的,毕竟内核资源有限。
四、进程优先级
1.进程优先级基本概念
- cpu资源分配、外设IO的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
2.查看系统进程
在linux或者unix系统中,用ps –al命令则会类似输出以下几个内容:
代码如下:
#include<stdio.h>
#include<unistd.h>
int main()
{
while(1)
{
printf("Hello Linux!\n");
sleep(1);
}
return 0;
}
我们很容易注意到其中的几个重要信息,有下:
- UID : 代表执行者的身份,用户标识符。
- PID : 代表这个进程的代号。
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号。
- PRI :prority代表这个进程可被执行的优先级,其值越小越早被执行。
- NI :代表这个进程的nice值。
3.PRI and NI
- PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高。
PRI的默认值一般是80。 - 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值。
NI的默认值一般是0。 - PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为PRI(new)=PRI(old)+nice。
- 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。
- 所以,调整进程优先级,在Linux下,就是调整进程nice值。
- nice其取值范围是-20至19,一共40个级别。
4.PRI vs NI
- 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
- 可以理解nice值是进程优先级的修正修正数据。
5.查看进程优先级的命令
- top。
top命令用于显示系统运行的进程信息,作用类似于windows中的任务管理器,只不过top不是图形化的,而是显示实时文本信息。
统计信息区域的下方显示了各个进程的详细信息。首先来认识一下各列的含义。PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
序号列名含义
PID 进程id
PPID 父进程id
RUSER Realusername
UID 进程所有者的用户id
USER 进程所有者的用户名
GROUP 进程所有者的组名
TTY 启动进程的终端名。不是从终端启动的进程则显示为?
PR 优先级
NInice 值。负值表示高优先级,正值表示低优先级
P 最后使用的CPU,仅在多CPU环境下有意义
%CPU 上次更新到现在的CPU时间占用百分比
TIME 进程使用的CPU时间总计,单位秒
TIME+ 进程使用的CPU时间总计,单位1/100秒
%MEM 进程使用的物理内存百分比
VIRT 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
SWAP 进程使用的虚拟内存中,被换出的大小,单位kb。
RES 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
CODE 可执行代码占用的物理内存大小,单位kb
DATA 可执行代码以外的部分(数据段+栈)占用的物理内存大小,单位kb
SHR 共享内存大小,单位kb
nFLT 页面错误次数
nDRT 最后一次写入到现在,被修改过的页面数。
S 进程状态。
D= 不可中断的睡眠状态
R= 运行
S= 睡眠
T= 跟踪/停止
Z= 僵尸进程
COMMAND 命令名/命令行
WCHAN 若该进程在睡眠,则显示睡眠中的系统函数名
Flags 任务标志,参考sched.h
- 进入top后按“r”–>输入进程PID–>输入nice值。
1.用top命令更改已存在进程的nice:
进入top后按“r”–>输入进程PID–>输入nice值。
- 输入top命令,记得要加sudo提升权限。
- 按r显示这个界面。
- 输入进程PID的值。
- 输入nice值。
这里就显示了修改以后的PRI和NI。
重要:
6.其他概念
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
五、环境变量
1.环境变量基本概念
- 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
- 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
- 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
2.为什么需要环境变量
环境变量(envirnment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
3.常见的环境变量
- PATH : 指定命令的搜索路径。
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)。
- SHELL : 当前Shell,它的值通常是/bin/bash。
4.查看环境变量的方法
echo $NAME //NAME:你的环境变量名称
5.测试PATH
代码如下:
#include<iostream>
int main()
{
std::cout<<"Hello world"<<std::endl;
return 0;
}
- 创建hello.cpp文件
- 对比./hello执行和之间hello执行
./hello运行
./目的是为了在当前路径下找到hello的可执行程序。
而如果直接运行hello那么将会发生错误:
-
为什么有些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行?
-
将我们的程序所在路径加入环境变量PATH当中, export PATH=$PATH:hello程序所在路。
这种方法只是在当前终端有效,离开当前终端就失效了。
-
对比测试使环境变量永久生效。
原来的.bash_profile是这样的。
经过修改后变成了:
-
还有什么方法可以不用带路径,直接就可以运行呢?
扩展知识:
6.测试HOME
用root和普通用户,分别执行 echo $HOME ,对比差异. 执行 cd ~; pwd ,对应 ~ 和 HOME 的关系。
root用户:
普通用户:
7.和环境变量相关的命令
- echo: 显示某个环境变量值。
- export: 设置一个新的环境变量。
- env: 显示所有环境变量。
- unset: 清除环境变量。
- set: 显示本地定义的shell变量和环境变量。
8.环境变量的组织方式
在C语言中我们学到了指针数组,就是存放指针的数组,那么存放的就是各种环境变量的首地址,通过找到环境变量的首地址就可以找到对于的环境变量。
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串。
9.通过代码如何获取环境变量
- 命令行第三个参数
代码如下:
#include<stdio.h>
int main(int argc,char* argv[],char* env[])
{
//argc : 命令行参数的个数, 本质上就是argv数组的元素个数
//argv :具体的命令行参数
// envp : 环境变量的值
for(int i=0;i<argc;i++)
{
printf("%s\n",argv[i]);
}
for(int i=0;env[i]!=NULL;i++)
{
printf("%s\n",env[i]);
}
return 0;
}
- 通过第三方变量environ获取
代码如下:
#include<stdio.h>
int main(int argc,char* argv[])
{
extern char** environ;
for(int i=0;environ[i]!=NULL;i++)
{
printf("%s\n",environ[i]);
}
return 0;
}
libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。
10.通过代码如何获取环境变量
常用getenv和putenv函数来访问特定的环境变量。
代码如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
printf("%s\n",getenv("PATH"));
return 0;
}
11.环境变量通常是具有全局属性
直接查看,发现没有结果,说明该环境变量根本不存在。
导出环境变量 export MYENV=“hello world”。
再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!
想象为什么?
代码如下:
#include<iostream>
#include<stdlib.h>
int main()
{
char* env=getenv("MYENV");
if(env)
{
std::cout<<env<<std::endl;
}
return 0;
}
总结
以上就是今天要讲的内容,本文仅仅进程概念后面的一些知识,而进程提供了大量能使我们快速便捷地处理数据的函数和方法。到这里,进程概念就结束了,后序将会有更重要的文章陆续更新,希望大家多多支持!另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。