1. 进程的状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
下面的状态在kernel源代码里定义:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
Z(zombie)-僵尸进程
其他的一些概念:
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进
2 僵尸进程
2.1 什么是僵尸进程
我们可以使用上文中的脚本开启进程监视器,同时使用上文使用的代码来监视父子进程的状态。
while : ; do ps axj | head -1 && ps axj | grep process.exe | grep -v grep;sleep 1;done
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{printf("process is running,pid(): %d,ppid: %d\n",getpid(),getppid());pid_t id=fork();//fork创建子进程,子进程fork返回0,父进程返回pidsleep(2);int num=100;if(id==0){//子进程int cnt=10;while(cnt--){num=999;printf("child running! id: %d, pid(): %d, ppid(): %d, num: %d\n",id, getpi d(),getppid(),num);sleep(1);}}else{while(1){sleep(1);printf("parent running!id: %d, pid(): %d,ppid(): %d, num: %d\n",id,getpid( ),getppid(),num);}}return 0;}
监视结果如下,我们看到刚开始两个进程都是睡眠状态??
不是的,是因为进程并非时时刻刻都在运行的,cpu运算速度太快,进程大部分时间都在等待其他资源,因此我们监视到的大部分都是睡眠状态。对我们而言他就是在运行的。
到后面子进程状态变为Z+(状态后有+代表进程已经就绪),这是因为子进程退出后,需要父进程接收他的退出信息,如果父进程一直没有接收,他就会一直处于僵尸状态。 (ps:kill杀不掉,ctrl+c也无法终结)
2.2 僵尸进程的危害
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
内存泄漏?是的!
2.3 如何避免僵尸进程的出现
这里需要父进程调用waitpid接口接收子进程的退出信息。后面细说。
3 孤儿进程
孤儿进程是父进程创建子进程后,父进程比子进程先退出,此时子进程就会成为孤儿。
3.1 看看孤儿进程
这里我们对上面的代码略作改动。我们在子进程结束前杀死父进程,这里用到 kill -9 pid命令。
我们发现,当杀死父进程后,子进程的父进程变为1,也就是OS。
3.2 孤儿进程被谁回收
子进程要有父进程回收,否则变为僵尸进程持续占有系统资源,那如果他的父进程死了子进程被谁回收呢?
父进程先退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,由init进程回收。
4. 进程优先级
4.1 基本概念
cpu资源分配的先后顺序,就是指进程的优先权(priority)。优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
4.2 查看系统进程
我们使用ps -l命令
我们很容易注意到其中的几个重要信息,有下:
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值
4.3 PRI与NI
PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
这样,当nice值为负值的时候,那么该程序将会优先级值变小,即其优先级会变高,则其越快被执行。
所以,调整进程优先级,在Linux下,就是调整进程nice值,nice其取值范围是-20至19,一共40个级别
注意:Linux下默认优先级为80,且每一次调整优先级都是从基准80开始。
4.4 更改优先级的命令top
进入top后按“r”–>输入进程PID–>输入nice值(不过一般不建议修改)。
5. 环境变量
5.1 基本概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性(即环境变量是可以被子进程继承的)
5.2 常见环境变量
PATH : 指定命令的搜索路径
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL : 当前Shell,它的值通常是/bin/bash
5.3 如何查看环境变量
1. env
env会在屏幕上显示出所有环境变量
2. echo $【环境变量名】
5.3 PATH
PATH
是一个环境变量,用于指定系统在哪些目录中查找可执行文件。当你在终端中输入一个命令时,系统会根据PATH
环境变量中列出的目录顺序逐个查找,直到找到匹配的可执行文件为止。通常,
PATH
的默认值包含了一些系统预定义的目录,比如/bin
、/usr/bin
等。你也可以自定义PATH
变量,将自己的目录添加到其中,以便系统能够在这些目录中查找到你自己编写的或者下载的可执行文件。
当我们在终端命令行键入ls ,pwd等命令可以直接执行,但我们自己写的代码为什么需要加./呢?
这是因为ls等可执行文件的路径在PATH环境变量中保存着,如果将我们的代码路径也添加到PATH环境变量中,那么我们的可执行文件也可以直接执行。
这里用到了export命令,稍后再谈。这个案例充分说明了PATH的重要性,那么如果我们一不小心修改了他该怎么办?
比如下面,我们一怒之下将PATH修改成了hahaha,发现我们的命令都没法用了。
这里不用担心,重新打开xshell,我们会发现PATH又重置成了开始的样子。
5.4 和环境变量相关的命令
1.echo: 显示某个环境变量值
2. export: 设置一个新的环境变量
3. env: 显示所有环境变量
4. unset: 清除环境变量
5. set: 显示本地定义的shell变量和环境变量
5.5 环境变量的组织方式
这里我们写一个示例代码。
我们并没有定义env,但我们的main函数却能使用。
同时fork出的子进程也是可以使用的。
5.6 通过系统调用获取环境变量
getenv();
#include <stdio.h>
#include <stdlib.h>
int main()
{
char * env = getenv("MYENV");
if(env){
printf("%s\n", env);
}
return 0;
}