Linux的进程和环境变量简单梳理

  搞清楚进程首先要理解操作系统是怎么管理进程的,它对上给用户提供良好的开发环境,对下要管理软硬件资源。操作系统管理就需要 先描述,再组织。

 1.进程的概念:

   首先要描述进程,当我们写的代码经过编译成为二进制文件在内存成为进程前,首先cpu会生成一个进程控制块——PCB来描述这个进程的信息,它里面包含了进程编号,进程状态,优先级等很多信息,它还会有一个指针来指向我们写的二进制文件,方便执行的时候能找到它。linux下的PCB是task_struct,它是一个结构体,是linux内核的一种数据结构,会被加载到内存当中。

  总之,进程就是:内核PCB数据结构对象+我们的数据和代码。

  2.深入理解进程

  作为root用户,可以使用ls /proc/来查看进程信息,普通用户也可以用top或者ps。

  2.1 父进程和子进程

  首先要知道每个进程都有一个编号,系统或者用户一般都是通过这个编号来管理或者查找进程,也就是进程编号——PID,还有一个编号就是父进程编号——PPID。这是两个独立二点进程,但是子进程是由父进程创建的。在我们写的C代码中,可以使用getpid()和getppid()来获取当前进程的PID和PPID,需要包含头文件#include<unistd.h>。

  我们可以在我们的代码中模拟实现父进程创建子进程,使用fork()来创建子进程

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
 int ret = fork();
 if(ret < 0){
 perror("fork");
 return 1;
 }
 else if(ret == 0){ //child
 printf("I am child : %d!, ret: %d\n", getpid(), ret);
 }else{ //father
 printf("I am father : %d!, ret: %d\n", getpid(), ret);
 }
 sleep(1);
 return 0;
}

在调用fork函数后,父进程返回它的PID,子进程返回0,在fork()执行完后,父进程和子进程根据不同的返回值进入到不同的if语句中,来执行不同的代码。注意的是,父进程和子进程相互独立,分别有各自的PCB,它们共享代码,但是父进程的数据不会共享,子进程则采用写时拷贝的方式拷贝数据,以此来达到两个进程互不影响。

  注意:如果返回值<0时说明创建子进程失败。

  2.2 关于进程的状态

  简单介绍一下在linux下的几个状态:

  1.R(运行状态):指正在运行或者在运行队列中的进程。

  2.S(睡眠状态):意味该进程正在等待事件完成,此处的睡眠可被打断。

  3.D(磁盘休眠状态):此进程的睡眠状态不可中断,必须等待IO事件结束才行。

  4.T(停止状态):我们可以通过发送SIGSTOP信号来使某个进程进入暂停状态,当发送SIGCONT使它再次开始,另外t(小写)也是暂停状态,但是它表示被追踪,一般调试的时候就会是t。

  5.X(死亡状态):这是一个返回状态,已经不能在任务列表里看到它。

  我们可以用 ps aux或者ps axj来查看进程的状态。

挂起

   另外说一下挂起,就是当内存资源不足时,操作系统会把处在阻塞状态的进程中的我们的代码数据先放到磁盘当中,以此来腾出内存空间,它只保留PCB,当内存资源够时,再把它放到内存中。

如果挂起也不能解决内存不足的问题时,操作系统就会开始杀掉进程。

  2.3 僵尸进程和孤儿进程

  僵尸进程(Z(zombie)):也是一个特殊的状态 Z,造成这个状态的原因是因为当子进程结束时,父进程没有读取到子进程的返回代码就会造成(比如wait()函数调用)。

  演示:

#include<stdio.h>
#include<unistd.h>

int main()
{
  int id=fork();
  if(id==0)
  {
   printf("i am son:[%d]\n",getpid());
    sleep(5);
  }
  else 
  {
    printf("i am father:[%d]\n",getpid());
    sleep(20);
  }
  return 0;
}

  上述代码中父进程没有处理子进程退出时的信息,就会导致僵尸进程。

  僵尸进程的危害:

  僵尸进程会以终止状态保留在进程表里,只要父进程还在运行且没有读取子进程的信息,这个状况就会一直持续。因为PCB要一直维护这个进程的信息,就会导致资源的浪费,内存泄露。

  孤儿进程:它跟僵尸进程是反过来的,它是父进程先退出,子进程还在,那么该子进程就会被1号(init)进程收养,并进行回收。

  2.4 进程的优先级

  概念:cpu资源分配的先后顺序,就是优先级(priority)

  PRI:就是进程的优先级,通常进程的优先级为80.

  NI:就是nice值,它可以改变PRI的值,范围是[-19,20],它修改的公式是PRI(new)=PRI(old=80)+NI。

  PRI越小说明优先级越高

  进程的拓展概念:

  1.竞争性:系统的进程数量很多,而cup又很少,所以进程之间是有竞争性的。为了高效完成任务,更加合理的分配资源,所以有了优先级。

  2.独立性:指的是进程独享各种资源,互不干扰。

  3.并行:多个进程在多个cpu下同时运行,称为并行。

  4.并发:多个进程在一个cup下采用进程切换的方式,在一段时间内使得多个进程运行,称为并发。

并发

  在日常中,我们用到的电脑都是单CPU,那么进程CPU就是通过并发的方式使得一段时间内多个进程运行,那么每次切换一个进程后,当这个进程下次再来的时候CPU怎么知道上次这个进程执行到哪了呢?其实进程在离开CPU的时候,会将自己的上下文数据保存好,甚至带走,为了未来进行恢复,CPU寄存器里会保存进程的上下文数据。

  还有就是当一个进程是一个死循环时,为什么我们也不会感觉到电脑卡死了呢?这是因为CPU在并发处理进程时会有一个时间片,每个进程运行的时间一旦超过了某个时间,就会被CPU拿下放在末尾排队并切换下一个进程。

    环境变量

  .概念

  环境变量:指在操作系统中,指定操作系统运行环境的一些参数。  比如我们在写代码的进行链接的时候,我们并不知道动态静态库在哪里,而编译器总能找到,就是因为环境变量帮助编译器查找。

  环境变量通常具有特殊用途,具有全局属性。

  常见的环境变量:

  PATH:指定命令的搜索路径。

  HOME:指定用户的主工作目录

  SHELL:当前Shell,它的值通常是/bin/bash

  弄懂环境变量首先要知道两张核心向量表:
  1.命令行参数表

  2.环境变量表

   环境变相相关的指令:

1. echo: 显示某个环境变量值
2. export: 设置一个新的环境变量
3. env: 显示所有环境变量
4. unset: 清除环境变量
5. set: 显示本地定义的shell变量和环境变量
环境变量具有全局属性,及子进程可以继承父进程的所有环境变量,但是本地变量无法继承。

  .命令行参数

  我们平时写的main函数其实是可以带参数的,而这些参数跟环境变量有关 
  我们在llinux下执行的每一个命令其实也是一个程序。
  我们平常写的命令中有带  -l  -a这种参数的,像这种参数其实就会被放入到一个指针数组中,然后在程序中根据我们传的参数来执行不一样的代码。
  
int main(int argc, char *argv[])
{
 extern char **environ;
 int i = 0;
 for(; environ[i]; i++){
 printf("%s\n", environ[i]);
 }
 return 0;
}

  argc就是指 有几个命令行参数,argv是一个指针数组,实质是一张命令行参数表,它存放的命令行参数,数组的最后一个存NULL。其中extern char **environ;就是获取了该进程的环境变量,后面进行了打印。

  然而main函数其实还可以有第三个参数,这个参数其实是获取环境变量的另一种方法。

#include <stdio.h>
int main(int argc, char *argv[], char *env[])
{
 int i = 0;
 for(; env[i]; i++){
 printf("%s\n", env[i]);
 }
 return 0;
}
#include <stdio.h>
int main(int argc, char *argv[])
{
 extern char **environ;
 int i = 0;
 for(; environ[i]; i++){
 printf("%s\n", environ[i]);
 }
 return 0;
}

  env也是一个指针数组,但它其实存的是这个进程运行时的环境变量,是一张环境变量表。

  另外getenv()可以用来获取特定的环境变量。

  

#include <stdio.h>
#include <stdlib.h>
int main()
{
 printf("%s\n", getenv("PATH"));
 return 0;
}

  本地变量&&内建命令:

  到这里我们会有一个初步的认识,我们平常所执行的ll,pwd等命令其实也是程序,那么它么要不要创建子进程呢?

  首先本地变量只会在bash内部有效,且不会被继承。

  那么假设我定义了一个val=1,这个本地变量,然后执行  echo $val  发现结果为1,这是为什么呢?

  echo难道不是bash的子进程吗?为什么会有val这个本地变量呢?

  答案还真不是,在linux下大部分的命令会创建子进程,叫常规命令,而少部分命令它不会创建子进程,它是由bash自己调用了写的函数,或者系统的函数,并没有创建子进程,这种命令叫做内建命令。 

 

进程地址空间 

地址空间 

  注意: static修饰的局部变量,编译的时候会被编译到全局数据区,因此它的生命周期是全局的,这就是它可以随着函数调用一直存在的原因,但是这个变量的作用域还是在这个函数内部,它的作用域依旧是局部的。

  栈的空间是向下增加的,堆区是向上增加的,有点像双向奔赴。

线性地址&&虚拟地址

  其实在此之前,我们写的所有的C/C++中的地址都不是物理地址,而是虚拟地址。我们可以看以下场景

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int g_val = 100;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int cnt = 5;
        // 子进程
        while(1)
        {
            printf("i am child, pid : %d, ppid : %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
            if(cnt) cnt--;
            else {
                g_val=200;
                printf("子进程change g_val : 100->200\n");
                cnt--;
            }
        }
    }
    else
    {
        // 父进程
        while(1)
        {
            printf("i am parent, pid : %d, ppid : %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
        }
    }
}

有一个全局变量g_val,然后创建一个子进程,之后在子进程中进行了写时拷贝,对这个g_val的值进行了修改,观察到以下现象。

 

 我们可以发现,这个g_val的地址都是0x40405c,但是为什么在子进程中是以200打印,父进程中是以100打印的呢?同一个地址解引用出来是不同的值,说明这根本就不是物理地址,而是虚拟地址(线性地址)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值