【Linux】——进程概念(万字解读)

打印 上一主题 下一主题

主题 673|帖子 673|积分 2019



一  冯诺依曼体系结构

在此之前,我们先要理解我们盘算机的冯诺依曼体系结构,因为是进程的基础
我们所有的操作其实都是基于如许一个模型,比如你在qq上,和别人发送消息,这个消息肯定是先通过输入装备举行输入,输入到存储器(这里只是单指内存),然后通过控制器和运算器的控制和盘算,把消息发送到输出装备(这里可以是网卡和显示器)
   那我们这些操作肯定需要有人控制吧,没有人控制,单凭一个cpu能去接受这么多信息?别完了,cpu的运行速度是很快的,如果这些事情都要他行止理,那就没有那么高效了。
  所有我们引出了操作系统这个概念

二  操作系统

概念


任何盘算机系统都包罗一个基本的步伐聚集,称为操作系统(OS)。笼统的理解,操作系统包罗:
   
内核(进程管理,内存管理,文件管理,驱动管理)
其他步伐(例如函数库,shell步伐等等)
  
计划OS的目的


   
与硬件交互,管理所有的软硬件资源
为用户步伐(应用步伐)提供一个良好的执行环境
  
定位


   
在整个盘算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
  由于我们的文件和进程错综复杂,都会占据内存,同时也有谁先举行谁后举行的序次,所以这里我们需要一个管理者去管理好这些资源,不然我们的电脑用几下就会导致死机,所以我们开机第一个启动的软件就是操作系统这个系统软件

管理

那我们操作系统是用什么方式去管理这些资源的呢?? 
这张图表示了我们在举行操作的外貌图,操作系统在管理底层的硬件的时间,并不能直接去访问,而是应该经过驱动步伐间接访问,所以操作系统在管理硬件资源的时间,只是拿到了硬件的数据,并没有直接见到了底层的硬件,既然我们拿到了数据,我们就可以通过数据去判断这个硬件步伐是否需要关闭,当然这些数据太多了,而且有些属于一个类型,有些不是,那既然如许,我们就可以通过描述这个硬件的基本属性,同时加上他的数据,如许归纳起来就可以统一管理了,但是这么多硬件,怎么去找呢??创建一个双链表,一个红黑树?把他们管理起来?,可行!!
所以经过以上步调就形成了一个先描述,再组织的操作
之前说了,只能从上到下访问,那我们电脑上的软件有各种功能,这些功能直接或者间接的操作了我们操作系统,那是不是这些软件可以肆意妄为??并不是!操作系统会防止用户去乱动操作系统里面的东西,但是它又得让用户去访问,于是就有了系统调用这个概念,由于系统调用的存在,使得我们可以在电脑上开发各种应用,使得我们的电脑功能性更全,这种选择性提供也是操作系统管理的一种手段

总结


盘算机管理硬件,先用struct结构体描述,再用多种数据结构举行组织,这里为什么是struct呢?因为linux是用c语言写的。
系统调用和库函数概念
   
在开发角度,操作系统对外会表现为一个整体,但是会暴露本身的部分接口,供上层开发利用,这部分由操作系统提供的接口,叫做系统调用。
系统调用在利用上,功能比力基础,对用户的要求相对也比力高,所以,故意的开发者可以对部分系统调用举行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者举行二次开发。
  三  进程

有了上面的认识,我们就可以再来认识进程的概念,一个进程也就是我们所运行的步伐,一个qq,一个微信,或者一个英雄同盟,他们都属于进程
所以一句话概括就是进程就是用来吃系统资源的——担当分配系统资源(CPU时间,内存)的实体。
进程也是受操作系统所管理的,那操作系统怎么管理?,和上面一样——先描述再组织
描述进程

对于进程我们得先描述。进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的聚集。
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
这个PCB里面就包罗了进程的诸多信息,比如:优先级,上下文数据,指针之类的
在linux下的PCB——task_struct
   ✨标示符: 描述本进程的唯一标示符,用来区别其他进程。
✨状态: 使命状态,退出代码,退出信号等。
✨优先级: 相对于其他进程的优先级。
✨步伐计数器: 步伐中即将被执行的下一条指令的地址。
✨内存指针: 包罗步伐代码和进程相关数据的指针,另有和其他进程共享的内存块的指针
✨上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
✨I/O状态信息: 包罗显示的I/O请求,分配给进程的I/O装备和被进程利用的文件列表。
✨记账信息: 大概包罗处理器时间总和,利用的时钟数总和,时间限制,记账号等。
✨其他信息
  
组织进程

所有运行在系统里的进程都以task_struct链表的情势存在内核里。

查看进程

查看一个进程,查看的是进程的信息
我们可以通过 ls /proc这个指令查看所有进程的信息

如果我们如今运行一个步伐

 如果我们想看他的信息,那么我们就利用一个看起来比力直观的下令(ps/top),因为/proc这个指令大概看起来并不直观,因为/proc 目录下的文件和目录提供的是系统的底层信息,它们的内容通常是以文本情势展示的,但格式和结构大概相对复杂。
这里我们利用ps查看,我们先运行这个步伐

这里大概查看到三个进程,但是只有第一个是我们要查的,其他两个是因为我们输入的指令也是一个进程,所以也把他们的进程信息输出了
 这就是进程的pid也就是进程的标识符,linux也是通过这个去区分进程的差别的
除了这种方法可以获取进程的pid,我们还可以通用系统调用去查看;

通过系统调用获取进程标示符

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include<sys/types.h>
  4. int main()
  5. {
  6.     printf("pid:%d\n",getpid());
  7.     return 0;
  8. }
复制代码
通过这段代码就可以获取到该进程的pid

运行这段步伐就可以得到该进程的pid,但是我们留意到其实他另有一个表示符 
这一段是他们的父进程的pid,也就是ppid,每个子进程都有一个父进程,当然,最大的父亲就是我们的bash
我们也可以通过系统调用把他的ppid也打印出来

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include<sys/types.h>
  4. int main()
  5. {
  6.     printf("pid:%d  ppid:%d\n",getpid(),getppid());
  7.     return 0;
  8. }
复制代码
通过系统调用创建进程-fork初识

对于fork也是一个系统调用,他的作用就是创建一个子进程
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. int main()
  5. {
  6.    int ret = fork();
  7.    if(ret < 0)
  8.    {
  9.    perror("fork");
  10.    return 1;
  11.    }
  12.    else if(ret == 0)
  13.    { //child
  14.    printf("I am child : %d!, ret: %d\n", getpid(), ret);
  15.    }
  16.    else
  17.    { //father
  18.    printf("I am father : %d!, ret: %d\n", getpid(), ret);
  19.    }
  20.    sleep(1);
  21.    return 0;
  22. }
复制代码
对于上面的代码来说,因为fork创建子进程会有一个返回值,如果创建成功对于子进程会返回0,对于父进程会返回子进程的pid,如果失败则是小于0的值
所以根据返回值的差别我们就可以去执行两块步伐

fork之后的代码子进程和父进程是共享的
那如今大概会狐疑为什么一个变量会有两个返回值,就算是两个进程,但是也就一个变量,一个变量的地址是固定,那么就不大概存在一个变量有两个值的环境
所以我们得先相识假造地址空间这个概念
文字分析:我们创建进程的时间会有操作系统先给进程描述一个PCB在linux里也叫tast_struct
这里面包罗了进程的信息,既然是信息那么就会包罗数据的存放地址,所以这里这就有一个假造地址的指针,指向这块内存,这块内存就是我们之前认识到的什么栈和堆,静态区,数据段...这类的空间

这个空间并不是现实数据存放的空间,它的出现只是为了方便管理,形成统一性,那我们怎么去找到物理内存呢(也就是现实的地址),这里采取的方式就算通过页表去映射,把假造内存和物理内存的地址一 一 对应起来,如果我们创建子进程,操作系统会创建子进程的PCB,然后会把父进程的假造内存的表继承下来,如许,子进程和父进程假造内存相同,但是有两份,因为子进程和父进程的代码相同(因为是继承下来的),又因为进程之间是相互独立互不干扰的,所以当子进程中的变量改变的时间,会产生暂时拷贝,也就是对于发生改变的数据,会在物理内存中产生一个新的位置,然后再通过页表的映射,改变对应的假造内存的映射,如许就完成了两个变量有差别的值,固然他们的假造地址是相同的,但是物理地址却是差别的
图片分析:


这里的进程空间其实是内存的一种数据结构 
留意:这里不要理解为是一个存在的空间,只是一种数据结构而已
进程地址空间就类似于一把尺子,尺子的刻度由0x00000000到0xffffffff,尺子按照刻度被划分为各个区域,例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个界限刻度,例如代码区的开始刻度与竣事刻度,如下图所示:

 
 
进程状态



运行状态

进程 PCB 被调理到 CPU 运行队列中且已被分配 CPU 资源,就叫做 ------ 运行状态
每个进程都有本身的PCB+本身的数据构成,当我们存在多个进程的时间,我们就需要用特定的数据结构把他们组织起来,比如链表,均衡树之类的,如许我们就可以很轻松的通过一个进程找到其他的进程
所以需要运行的进程,会把他们的PCB放在CPU的运行队列中,然后通过相应的数据结构组织起来,为了更加形象下面给出一张图片分析

这张图就是用的双向链表把他们组织起来的,大概还会用到其他的数据结构,因为操作系统的组织比这个可复杂太多了 
如果这个进程需要执行了,就把代码和数据放进CPU中就行了
那如果这个进程运行了很久呢??那不是一直等他完成?


  • 不是,每一个进程都有一个叫做:时间片的概念! 当时间大概是在10 ms左右。所以并不是一个进程一直在执行,而是这多个进程在一个时间段内所有代码都会被执行 —— 这就叫做【并发执行】 
  • 所以呢这就一定会存在大量的进程 被CPU放上去、拿下来的动作 —— 这就叫做【进程切换】 
这个速度很快,我们感受不到是很正常的
阻塞状态



  • 阻塞 就是 进程 因等待某种条件停当,而导致的一种不推进状态(比如等待 键盘输入)。
  • 通俗的来说 阻塞 就是 进程卡住了原因就是缺少资源
留意:这里要和上面运行状态的进程切换区分开,上面的是在运行,不缺条件,这里是因为条件的缺失而去等待资源
那么进程需要什么资源呢?


  • 比如 磁盘、网卡、显卡 等各种外设
  • 假设你如今想在 steam 上下载游戏,当你点击下载按钮后提示磁盘空间不敷,此时是无法运行 steam下载 这个进程的,因为此 进程 需要等待足够大的 磁盘资源
  • 此时我们就称此 进程 为 阻塞 状态
和上面的留意关联起来
   总结:进程阻塞就是不被调理
  

  • 此时 PCB(task_struct) 就会被设置为 阻塞状态,并链入等待的资源提供的等待队列
  • 没错,这里的等待队列 类似于 CPU 运行队列 

 挂起状态



  • 当 CPU 资源告急时,将 进程的数据和代码 交换至 磁盘 中挂起,此时内存中只有 PCB
     
  • 挂起 可以看作一种特别的 -- 阻塞状态
大概你买苹果电脑的时间就会考虑内存够不够用的标题,也许你从网上看了很多,有说够用,有说不够用,所以这里很多人提到的词就swap,这里的swap就是当我们的内存不够用的时间,会把代码和数据放进磁盘里面,等到需要用的时间,再拿出来,这个操作就算swap,这个是有成本的,所以多次的swap大概会导致电脑性能下降,发热等等

   

  • 当盘算机资源比力吃紧时,操作系统一定要确保自身不会因为资源的告急而瓦解,所以就会将一些等待资源(阻塞)的进程的代码和数据交换到磁盘的 swap分区 中,这个过程称为唤出
  • 当需要调理此进程时,就会将磁盘的 swap分区 中保存的内容换回到内存中,这个过程称为唤入
  
留意:交换的是进程的代码和数据,不是PCB!!如果PCB被交换出内存了,那操作系统怎样管理呢?
所以当某个进程的代码和数据不在内存中,而被换出到磁盘上时,该进程就为挂起状态。

linux系统下的7种进程状态

运行状态

我们先看一段代码
  1.    #include <stdio.h>
  2.    #include <unistd.h>
  3.    
  4.    int main(void)
  5.    {
  6.       while(1);                                                               
  7.       {
  8.           printf("Hello process, pid: %d\n",getpid());
  9.           sleep(1);
  10.       }
  11.       return 0;
  12.    }
复制代码

从图中可以看到状态是S+,现实上却是R+才表示是运行状态,哪为什么 会如许呢?
再看一段代码就知道了
  1.    #include <stdio.h>
  2.    #include <unistd.h>
  3.    
  4.    int main(void)
  5.    {
  6.       while(1);                                                               
  7.       {
  8.          //printf("Hello process, pid: %d\n",getpid());
  9.          //sleep(1);
  10.       }
  11.       return 0;
  12.    }
复制代码
 

如许就变成了运行状态


  • 原因就在于 printf 打印语句它是属于 IO流 的一种,第一次因为是循环的缘故,它一直在等IO装备停当,所以其进程状态就一直为 S+,对应的即是在操作系统中的【阻塞状态】;但是当我们去掉 printf 这种IO流之后呢,它就是在纯纯运行,没有IO,那也就变成了 R 状态


  • 这里的 R+ 代表的就是这个进程是在前台运行的,所以我们在输入任何指令后不会对其造成 任何的影响



  • 那如果我们不以正常的方式去启动这个进程的话,其进程的状态就会不一样了,可以看到我在 ./mytest 的后面加上了一个 &;那么其状态变成了 R,此代表的意思就是这个进程它是运行在了【背景】的



  •  不外呢,R状态并不代表这个进程就在运行,而代表其在运行队列中排队而已.
所以总的一句话来说就是
    "+"代表是前台运行,无"+"代表背景运行,背景运行时可在下令行继承输入指令并执行,但无法用ctrl+c竣事,需要用kill -9 pid杀死。想要背景运行某个步伐就在后面加"&",如:./test & 
  
浅度睡眠状态

这个状态上面也提到过就上S+这个状态,等待io装备的输入
  1.    #include <stdio.h>
  2.    #include <unistd.h>
  3.    
  4.    int main()
  5.    {
  6.        int a = 0;
  7.        printf("Enter# ");
  8.        scanf("%d", &a);
  9.    
  10.        printf("echo : %d\n", a);
  11.        return 0;                                                                        
  12.    }
复制代码



  • 将该进程运行起来我们可以看到其是出于 S+ 的状态,因为【shell】此时正在等待用户的输入,这个就对应到了我们上面所讲到的 阻塞状态
深度睡眠状态

   除了【浅度睡眠】之外呢,另有一种叫做【深度睡眠】,它们俩呢,都是 阻塞状态
  

  • 对于浅度睡眠来说,之所以称为 “浅度”,是有原因的:也就是处于这种状态的进程容易被唤醒。例如说我们在上面所讲到的这个处于阻塞状态的进程,我们利用 kill -9 PID
     向这个进程发送【9号信号】,那么这个进程就被杀死了,你也可以以为被唤醒了
一个进程处于深度睡眠状态(disk sleep),表示该进程不会被杀掉,即便是操作系统也不行,只有该进程主动唤醒才可以恢复。该状态偶然候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的竣事。
例如,某一进程要求对磁盘举行写入操作,那么在磁盘举行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)


  • 如果在这个过程中,操作系统能够杀死该进程,那么就有大概丢失数据。
所以也就是因为如许,操作系统不敢去打搅这个进程

停息状态



  • 首先我们要通过下面这句下令来查看一下对应的进程信号
   kill - l
  


我们利用的就是18,19信号



  • 停息进程
   kill -19 PID
  

  • 启动进程
   kill -18 PID
  那大概就有疑问了,这个停息状态和上面的睡眠状态的区别是什么呢??

  • stopped状态 进程 完全停息了, 其不会再接收任何信号了
  • 一个进程通过 stopped 状态可以控制另一个
  • S 和 D 一定是在等待某种资源,而 T状态 大概在等待某种资源,也大概被其他进程控制

死亡状态

死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被开释,该进程也就不存在了,所以你不会在使命列表当中看到死亡状态(dead)。


  • 第一种方法就是向这个进程发送9号信号,就可以杀掉这个进程


  • 第二种方法就是通过这个进程的名称来杀掉它
  1. kill -9 PID
复制代码
  1. killall 进程名
复制代码
僵尸状态

前面说到,一个进程如果正在等待其退出信息被读取,那么我们称该进程处于僵尸状态。而处于僵尸状态的进程,我们就称之为僵尸进程
之所以有这个状态就是因为,我门需要对退出的进程举行资源的查看,不能直接就退出了,大概该进程里有我们需要的信息或者资源
例如,对于以下代码,fork函数创建的子进程在打印5次信息后会退出,而父进程会一直打印信息。也就是说,子进程退出了,父进程还在运行,但父进程没有读取子进程的退出信息,那么此时子进程就进入了僵尸状态。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main()
  5. {
  6.         printf("I am running...\n");
  7.         pid_t id = fork();
  8.         if(id == 0){ //child
  9.                 int count = 5;
  10.                 while(count){
  11.                         printf("I am child...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
  12.                         sleep(1);
  13.                         count--;
  14.                 }
  15.                 printf("child quit...\n");
  16.                 exit(1);
  17.         }
  18.         else if(id > 0){ //father
  19.                 while(1){
  20.                         printf("I am father...PID:%d, PPID:%d\n", getpid(), getppid());
  21.                         sleep(1);
  22.                 }
  23.         }
  24.         else{ //fork error
  25.         }
  26.         return 0;
  27. }
复制代码
运行该代码后,我们可以通过以下监控脚本,每隔一秒对该进程的信息举行检测。
  1. while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;echo "######################";sleep 1;done
复制代码

僵尸进程的危害
1.僵尸进程的退出状态必须一直维持下去,因为它要告诉其父进程相应的退出信息。但是父进程一直不读取,那么子进程也就一直处于僵尸状态。
2.僵尸进程的退出信息被保存在task_struct(PCB)中,僵尸状态一直不退出,那么PCB就一直需要举行维护。
3.如果一个父进程创建了很多子进程,但都不举行回收,那么就会造成资源浪费,因为数据结构对象本身就要占用内存。
4.僵尸进程申请的资源无法举行回收,那么僵尸进程越多,现实可用的资源就越少,也就是说,僵尸进程会导致内存走漏。
   进程一般退出的时间,一般其不会立即彻底退出。如果父进程没有主动回收子进程信息,子进程会一直让本身处于Z状态,这也是为了方便后续父进程读取子进程的相关退出效果。
  孤儿状态

在Linux当中的进程关系大多数是父子关系,若子进程先退出而父进程没有对子进程的退出信息举行读取,那么我们称该进程为僵尸进程但如果父进程先退出,那么将来子进程进入僵尸状态时就没有父进程对其举行处理,此时该子进程就称之为孤儿进程。
如果一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存走漏。因此,当出现孤儿进程的时间,孤儿进程会被1号init进程领养,此后当孤儿进程进入僵尸状态时就由int进程举行处理回收。

例如,对于以下代码,fork函数创建的子进程会一直打印信息,而父进程在打印5次信息后会退出,此时该子进程就变成了孤儿进程。
同时我们一直打印子进程的PID与PPID,如许便于观察征象
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main()
  5. {
  6.         printf("I am running...\n");
  7.         pid_t id = fork();
  8.         if(id == 0){ //child
  9.                 int count = 5;
  10.                 while(1){
  11.                         printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid(), count);
  12.                         sleep(1);
  13.                 }
  14.         }
  15.         else if(id > 0){ //father
  16.                 int count = 5;
  17.                 while(count){
  18.                         printf("I am father...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
  19.                         sleep(1);
  20.                         count--;
  21.                 }
  22.                 printf("father quit...\n");
  23.                 exit(0);
  24.         }
  25.         else{ //fork error
  26.         }
  27.         return 0;
  28. }
复制代码



四  进程优先级

基本概念

什么是优先级?
优先级现实上就是获取某种资源的先后序次,而进程优先级现实上就是进程获取CPU资源分配的先后序次,就是指进程的优先权(priority),优先权高的进程有优先执行的权利。
优先级存在的原因?
优先级存在的主要原因就是资源是有限的,而存在进程优先级的主要原因就是CPU资源是有限的,一个CPU一次只能跑一个进程,而进程是可以有多个的,所以需要存在进程优先级,来确定进程获取CPU资源的先后序次。

查看进程优先级信息

  1. ps -al
复制代码

列出的信息当中有几个告急的信息,如下:
   

  • UID:代表执行者的身份。
  • PID:代表这个进程的代号。
  • PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号。
  • PRI:代表这个进程可被执行的优先级,其值越小越早被执行。
  • NI:代表这个进程的nice值。
  PRI与NI
1.PRI代表进程的优先级(priority),通俗点说就是进程被CPU执行的先后序次,该值越小进程的优先级别越高。
2.NI代表的是nice值,其表示进程可被执行的优先级的修正数值。
3.PRI值越小越快被执行,当参加nice值后,将会使得PRI变为:PRI(new) = PRI(old) + NI。
4.若NI值为负值,那么该进程的PRI将变小,即其优先级会变高。
5.调整进程优先级,在Linux下,就是调整进程的nice值。
6.NI的取值范围是-20至19,一共40个级别。
  1. 注意: 在Linux操作系统当中,PRI(old)默认为80,即PRI = 80 + NI。
复制代码
通过top下令更改进程的nice值

top下令就相称于Windows操作系统中的使命管理器,它能够动态及时的显示系统当中进程的资源占用环境。

利用top下令后按“r”键,会要求你输入待调整nice值的进程的PID。

 输入进程PID并回车后,会要求你输入调整后的nice值。
输入nice值后按“q”即可退出,如果我们这里输入的nice值为10,那么此时我们再用ps下令查看进程的优先级信息,即可发现进程的NI变成了10,PRI变成了90(80+NI)。

   留意: 如果想将NI值调为负值,也就是将进程的优先级调高,需要利用sudo下令提拔权限。 
  
通过renice下令更改进程的nice值

利用renice下令,后面跟上更改后的nice值和进程的PID即可。


   四个告急概念
竞争性: 系统进程数量众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成使命,更合理竞争相关资源,便有了优先级。
  
  独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
  
  并行: 多个进程在多个CPU下分别同时举行运行,这称之为并行。
  
  并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
  五  环境变量

基本概念

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
   例如,我们编写的C/C++代码,在各个目标文件举行链接的时间,从来不知道我们所链接的动静态库在哪里,但是还是可以链接成功,天生可执行步伐,原因就是有相关环境变量帮助编译器举行查找。
  常见环境变量



  • PATH: 指定下令的搜索路径。
  • HOME: 指定用户的主工作目录(即用户登录到Linux系统中的默认所处目录)。
  • SHELL: 当前Shell,它的值通常是/bin/bash。

查看环境变量的方法

我们可以通过echo下令来查看环境变量,方式如下:
   echo $环境变量名称 
  测试PATH

大家有没有想过如许一个标题:为什么执行ls下令的时间不消带./就可以执行,而我们本身天生的可执行步伐必须要在前面带上./才可以执行?
   要执行一个可执行步伐必须要先找到它在哪里,既然不带./就可以执行ls下令,分析系统能够通过ls名称找到ls的位置,而系统是无法找到我们本身的可执行步伐的,所以我们必须带上./,以此告诉系统该可执行步伐位于当前目录下。
   这和linux一切皆文件就是一个道理了,指令也是一个可执行步伐,我们需要找到它才能执行它
而系统就是通过环境变量PATH来找到ls下令的,查看环境变量PATH我们可以看到如下内容:

 上图就是默认的搜索路径
可以看到环境变量PATH当中有多条路径,这些路径由冒号隔开,当你利用ls下令时,系统就会查看环境变量PATH,然后默认从左到右依次在各个路径当中举行查找。
而ls下令现实就位于PATH当中的某一个路径下,所以就算ls下令不带路径执行,系统也是能够找到的。

那既然是如许,我们就可以把我们写的步伐的路径加上去,如许,默认搜索也可以搜索到我们的步伐了
方式一:将可执行步伐拷贝到环境变量PATH的某一路径下。
 
  1. sudo cp proc /usr/bin
复制代码
因为涉及到权限,所以需要sudo下令
方式二:将可执行步伐所在的目录导入到环境变量PATH当中。
将可执行步伐所在的目录导入到环境变量PATH当中,如许一来,没有指定路径时系统就会来到该目录下举行查找了。
  1. export PATH=$PATH:/home/cl/dirforproc/ENV
复制代码


测试HOME

任何一个用户在运行系统登录时都有本身的主工作目录(家目录),环境变量HOME当中即保存的该用户的主工作目录。

 

测试SHELL

我们在Linux操作系统当中所敲的各种下令,现实上需要由下令行解释器举行解释,而在Linux当中有许多种下令行解释器(例如bash、sh),我们可以通过查看环境变量SHELL来知道本身当前所用的下令行解释器的种类

而该下令行解释器现实上是系统当中的一条下令,当这个下令运行起来变成进程后就可以为我们举行下令行解释。

 和环境变量相关的下令

1、echo:显示某个环境变量的值。
2、export:设置一个新的环境变量。
3、env:显示所有的环境变量。

4、set:显示本地界说的shell变量和环境变量。

 留意:more 下令类似 cat ,不外会以一页一页的情势显示,更方便利用者逐页阅读,而最基本的指令就是按空缺键(space)就往下一页显示,按 b 键就会往回(back)一页显示,而且另有征采字串的功能(与 vi 相似)

5、unset:清除环境变量。 

环境变量的组织方式


   每个步伐都会收到一张环境变量表,环境表是一个字符指针数组,每个指针指向一个以’\0’末端的环境字符串,最后一个字符指针为空。
  通过代码获取环境变量

main函数其实有三个参数,只是我们平时基本不消它们,所以一般环境下都没有写出来。
我们可以在Windows下的编译器举行验证,当我们调试代码的时间,如果一直利用逐步调试,那么最终会来到调用main函数的地方。


我们可以在linux环境下编写以下代码



如今我们来说说main函数的前两个参数,main函数的第二个参数是一个字符指针数组,数组当中的第一个字符指针存储的是可执行步伐的位置,其余字符指针存储的是所给的多少选项,最后一个字符指针为空,而main函数的第一个参数代表的就是字符指针数组当中的有用元素个数
 

 main函数的第三个参数接收的现实上就是环境变量表,我们可以通过main函数的第三个参数来获取系统的环境变量。


 除了利用main函数的第三个参数来获取环境变量以外,我们还可以通过第三方变量environ来获取。

这里的extern修饰的变量是指这个变量的的声明在其他文件中,并不在当前文件和头文件中

通过系统调用获取环境变量

除了通过main函数的第三个参数和第三方变量environ来获取环境变量外,我们还可以通过系统调用getenv函数来获取环境变量。
getenv函数可以根据所给环境变量名,在环境变量表当中举行搜索,并返回一个指向相应值的字符串指针。

例如,利用getenv函数获取环境变量PATH的值。

因为是字符串指针返回,所以打印的时间用%s打印就行了

 
六  Linux2.6内核进程调理队列


一个CPU拥有一个runqueue

如果有多个CPU就要考虑进程个数的父子均衡标题。
 
优先级

queue下标分析:


  • 平凡优先级:100~139。
  • 及时优先级:0~99。
我们进程的都是平凡的优先级,前面说到nice值的取值范围是-20~19,共40个级别,依次对应queue当中平凡优先级的下标100~139。
留意: 及时优先级对应及时进程,及时进程是指先将一个进程执行完毕再执行下一个进程,如今基本不存在这种机器了,所以对于queue当中下标为0~99的元素我们不关心。
也就是说,这种方式的机器太慢了,因为需要等一个进程完毕以后才弄下一个进程,盘算机的使命但是很重的,这种时间的损耗对于盘算机来说是很大的

活动队列

时间片还没有竣事的所有进程都按照优先级放在活动队列当中,其中nr_active代表总共有多少个运行状态的进程,而queue[140]数组当中的一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进程排队调理。
调理过程如下:
  1.从0下标开始遍历queue[140]。
  2.找到第一个非空队列,该队列必定为优先级最高的队列。
  3.拿到选中队列的第一个进程,开始运行,调理完成。
  4.接着拿到选中队列的第二个进程举行调理,直到选中进程队列当中的所有进程都被调理。
  5.继承向后遍历queue[140],寻找下一个非空队列。

留意:bitmap[5]:queue数组当中一共有140个元素,即140个优先级,一共140个进程队列,为了进步查找非空队列的服从,就可以用5 × 32个比特位表示队列是否为空,如许一来便可以大大进步查找服从。这里相称于位图的结构,1代表有,0代表没有
   总结: 在系统当中查找一个最合适调理的进程的时间复杂度是一个常数,不会随着进程增多而导致时间成本增加,我们称之为进程调理的O(1)算法。
  过期队列


  过期队列和活动队列的结构相同。
  过期队列上放置的进程都是时间片耗尽的进程。
  当活动队列上的进程被处理完毕之后,对过期队列的进程举行时间片重新盘算。

active指针和expired指针



  active指针永远指向活动队列。
  expired指针永远指向过期队列。

由于活动队列上时间片未到期的进程会越来越少,而过期队列上的进程数量会越来越多(新创建的进程都会被放到过期队列上),那么总会出现活动队列上的全部进程的时间片都到期的环境,这时将active指针和expired指针的内容交换,就相称于让过期队列变成活动队列,活动队列变成过期队列,就相称于又具有了一批新的活动进程,如此循环举行即可。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

缠丝猫

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表