Linux详解 — 进程管理2 (进程状态、环境变量与命令行参数)

进程状态

进程状态的查看

	ps aux
	ps axj
#一般配合grep进行使用
	ps axj | grep test	#test就是进程名(举的例子)

进程状态的种类

  • R (running):运行状态
  • S (sleeping):睡眠状态
  • D (disk sleep):磁盘休眠状态
  • T (stopped):停止状态
  • X (dead):死亡状态
  • Z (zombie):僵尸状态

下面我会详细解释每一种状态对应的含义:

并发与并行

  并发:在单处理器(一个CPU)中,在同一时刻只能有一个线程正在运行,只不过OS可以通过不断的切换线程,让宏观上看起来就像是同时运行多个线程一样。
  并行:在多处理器(多个CPU)中,在同一时刻可以执行与CPU数量相同个数的线程

R运行状态

1个CPU可以同时存在多个R状态的进程,但是1个CPU在同一时刻只能运行一个进程!
因此,即使进程是R状态,不一定代表它正在运行,而是代表可以被调度!
(存在调度队列,R状态的进程要么正在运行要么在调度队列当中等待被调度)

S休眠状态:(可中断的睡眠状态/浅度睡眠)

S状态代表该进程正在等待某种事件的发生。
(S状态下的进程可以被 Kill 掉。浅度睡眠状态可以被唤醒,也可以被杀掉
ps:【在程序中使用sleep(),可以让该进程变为S状态】
在这里插入图片描述

D磁盘休眠状态(不可中断的睡眠状态/深度睡眠)

  D状态是不可中断的睡眠状态,这就一位置它无法被杀死。比较常见的例子就是等待硬件设备的I/O响应,在等待期间它就是处于D状态,我们只能等待它的I/O结束。

T停止状态

 我们可以通过发送 SIGSTOP(停止) 信号去将进程设置为停止状态。
这个暂停的进程可以通过发送 SIGCONT(继续) 信号让进程继续运行。
小细节:
当一个进程被暂停后,处于T状态。此时向它发送退出信号(比如: SIGINT信号),它并不会立刻就处理退出信号,而是会等到下一次继续运行时(收到SIGCONT后),再进行退出操作。
//但是kill -9还是可以立刻杀死它的

X死亡状态

  这只是一个返回状态,在任务表中看不到该状态。

Z僵尸状态 (产生原因 / 为什么会有 / 避免方法)

问题1:什么是僵尸状态?
  子进程先退出,父进程运行,且父进程没有读取子进程退出的退出码时,那么子进程就会处于僵尸状态(Z)。 //一般通过让父进程调用 wait 或 waitpid函数 对子进程的资源进行回收从而阻止僵尸进程的产生。

问题2:僵尸状态产生的原因?
  当子进程退出时,在系统层面上,曾经申请的资源并不会被立即释放,因为要供父进程进行读取,只要父进程没有进行读取,那么子进程就处于僵尸状态。
<---------------换个角度理解-------------->
  进程退出的信息(退出码)是会被暂时保存起来的(保存在task_struct)。
  如果没有人读取,那么该task_struct相关数据不应该被释放掉。此时的状态为Z状态。

问题3:为什么要有僵尸状态?
  为了给子进程的调用方返回当前进程完成任务时的样子。
(因为进程被创建的目的就是为了完成某种工作,所以任务完成时,为了让调用方知道任务完成的怎么样)

问题4:我们该怎么避免僵尸进程? (3种方法)
  ①wait方法:让父进程调用wait或waitpid函数去回收子进程的资源
  ②信号法:借助信号的机制,子进程退出的时候会给父进程发送SIGCHLD信号,我们可以捕捉SIGCHLD信号 [signal(SIGCHLD, handler);],然后再自定义SIGCHLD信号的处理方式,在SIGCHLD的信号处理函数中调用wait/waitpid函数进行子进程资源的回收。这样的好处就在于,父进程可以专心做自己的事情,不用关心子进程了,子进程终止的时候会通知父进程,父进程在信号处理方法中去调用wait函数清理子进程。
  ③孤儿进程法:在子进程终止前,让父进程先终止,这样就不会生成僵尸进程了,子进程会变为孤儿进程,此时由1号进程对该子进程的资源进行回收。(具体的操作可以是: 连续fork两次,子进程在创建完孙子进程后,kill掉子进程,这样孙子进程就会由init进程领养了)
//每个方法名字是我自己取的

孤儿进程
  当父进程先退出时,子进程就被称之为“孤儿进程”。
  孤儿进程会被 1号进程 (init进程) 领养最后也会被init进程回收
//就相当于将父进程换成了 init进程,然后继续正常运行。
//孤儿进程没有危害。


演示僵尸状态

#include <iostream>                                                                                  
#include <cstdlib>    
#include <unistd.h>    
using namespace std;    
    
int main()    
{    
  if(!fork())    
  {    
    cout << "Child Exit!" << endl;     
    exit(1);    
  }    
  //父进程没有回收子进程的资源,子进程此时就是僵尸状态了
  sleep(1000);    
  return 0;    
}

  下图就是代码运行的结果,子进程的进程状态就是僵尸状态。
在这里插入图片描述


环境变量

问题:环境变量是什么?
  环境变量一般指在操作系统中用来指定操作系统运行环境的一些参数,并且它们一般是具有一些功能的变量。
问题:为什么要有环境变量?环境变量有什么用?
  环境变量的使用可以让我们的操作更加方便。如果没有环境变量的话,很难查看一些状态,比如当前目录,当前用户…

常见的环境变量的介绍

PATH

  PATH: 指定命令的搜索路径
问题:为什么我们在Linux执行命令的时候不需要带 路径 ?比如: ls命令
  因为我们有个环境变量PATH。
  我们执行命令的时候,会先在特定的目录(PATH变量中的所有路径)里面去找命令。默认配置的PATH中的路径↓↓↓
在这里插入图片描述
问题:怎么样把我们自己写的程序也变得像系统命令一样?
只需要把我们的程序所在的路径导入到PATH环境变量中即可!
方法1(不推荐:将自己写的程序 cp 到PATH的路径当中,这样就可以直接输入命令去运行了。
举个🌰:
  自己写了一个myproc的程序,需要./myproc才能运行。但是将它cp到PATH路径后,直接输入myproc就可以运行了
方法2export PATH=$PATH:[路径]
  自己所添加的路径,会在下次重新启动shell时消失

PWD

  查看当前的工作目录

HOME

  查看当前用户的主工作目录

SHELL

  查看当前的bash,它的值通常是/bin/bash

环境变量的相关命令 (如何导入环境变量)

	echo + $环境变量:显示某个环境变量的值
			echo $SHELL
			输出: /bin/bash
			
	名称=(值包括字符串/数字) :设置一个本地变量    //前面加上export后,即可变成环境变量
			MYVAL=2333
			export MYVAL2=666		#可以结合下面的图一起食用
			
	export:设置一个新的环境变量
			#对于前面的本地变量MYVAL,我们可以通过export的方法把它变为环境变量
			export MYVAL

	env:查看所有环境变量
	set:显示本地定义的shell变量和环境变量
	unset:清除某个环境变量

一、直接使用export 变量=值 设置我们自己的环境变量
在这里插入图片描述

二、如果在使用 变量=值 的时候没有加export。设置的就是本地变量
在这里插入图片描述
三、将我们设置的本地变量转为环境变量的话,还可以在其前面加一个export
在这里插入图片描述

本地变量与环境变量

  本地变量:(不加export)
  只在本进程(bash)内有效
  环境变量:(全局的)
  所有进程都能看到这个变量(指环境变量),且都可以使用。

环境变量的组织方式

  环境变量表也是一个字符指针数组,每个指针都指向一个以 '/0’结尾的环境变量字符串。
字符指针数组的最后一个元素存放的是NULL
在这里插入图片描述

环境变量的获取 (程序中)

①:通过main函数的参数中的char* envp[]数组

	int main(int argc, char* argv[], char* envp[])
	{
		int i = 0;
		for(; envp[i]; ++i)
			printf("%sn", envp[i]);
		return 0;
	}

②:通过第三方变量environ获取

	int main()
	{
		extern char** environ;
		int i = 0;
		for(; environ[i]; ++i)
			printf("%sn", environ[i]);
	}

③:通过系统调用getenv与putenv

	char* getenv(const char* name);        //均包含于<stdlib.h>
	int putenv(char* string);

getenv:从已有的环境变量中获取指定环境变量名称的环境变量。
putenv:string字符串的格式为name=value。当环境变量中不存在该名称,则添加字符串到环境变量中;如果已经存在了name环境变量,则将name环境变量的值改为value。

	int main()
	{
		printf("%sn", getenv("PATH"));		//getenv中指定获取环境变量的名称
		return 0;
	}


命令行参数

问题:为什么在linux中输入命令时可以带选项? 如:ls -l
  命令的本质是可执行程序,而你在输入命令后,就相当于把该程序运行起来了。而输入命令时所携带的参数会被收入到命令行参数中,main函数可以接收到这些参数,从而会去执行相应的操作。
argc:代表 在命令行中输入的参数的个数
argv:存储所有命令行参数(argv[0] 存储的是程序名) //argv是个字符指针数组
懂了以上的概念后,你也可以写一个能够接收命令行参数的小程序了!这里给出命令行参数的获取方法。
在这里插入图片描述
将程序编译链接后,./程序 [参数选项] [参数选项]  #像这样在后面携带的参数就会被收入到命令行参数argv数组中了
问题:为什么我们可以向main函数中传递命令行参数?
  main函数是由mainCRTstartup函数调用的,CRT是由操作系统内核调用的。所以可以向main函数传递环境变量。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>