初始进程2.0

Posted WoLannnnn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初始进程2.0相关的知识,希望对你有一定的参考价值。

进程优先级

基本概念

  • cpu资源分配的先后顺序,就是指进程的优先权(priority)。
  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
  • 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整
    体性能。

优先级 vs 权限(自己的理解)

优先级:使用“事物”的先后顺序

权限:能不能使用某种“事物”

当被管理对象多时,优先级才有价值

优先级是通过整数来表示的,一般数值越小,优先级越高。就像我们在食堂排队吃饭。

查看系统进程

在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:

我们很容易注意到其中的几个重要信息,有下:

UID : 代表执行者的身份(编号)
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值,优先级的修正值

PRI and NI

  • PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小,进程的优先级别越高
  • 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为: PRI(new)=PRI(old)+nice
  • 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
  • 所以,调整进程优先级,在Linux下,就是调整进程nice值
  • nice其取值范围是-20至19,一共40个级别。

所有进程的pri一般默认为80,ni默认为0

需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
可以理解nice值是进程优先级的修正数据。

查看进程优先级的命令

用top命令更改已存在进程的nice:

top 进 #入任务管理器
进入top后按“r”–>输入进程PID–>输入nice值 ——对应进程的PID就会变成默认的pri + nice值

除非特殊情况,否则尽量不要修改nice值

进程相关的其他概念

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级

  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰.子进程如果崩溃,是不会影响父进程的

  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

    一个进程占有CPU,就会一直运行到进程结束吗?

    不会,如果这样的话,当我们在服务器上跑一个死循环的程序时,服务器就崩了

    每一个进程都有自己的时间片。调度器通过时间片浮动做到“雨露均沾”,实现并发,一个进程的时间片结束,就换另一个进程运行。

    进程在运行时,有两种情况:

    1.进程在时间片内,不可被抢占

    2.进程在时间片内,可以被抢占。 一般被优先级高的或者紧急情况抢占

  • 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行

环境变量

基本概念

  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,且在系统当中通常具有全局特性

常见环境变量

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

查看环境变量方法

echo $NAME //NAME:你的环境变量名称

测试PATH

当服务器执行一条命令时:

  1. 找到这条命令

  2. 运行它

  1. 创建hello.c文件

    #include <stdio.h>
    int main()
    
        printf("hello world!\\n");
        return 0;
    
    
  2. 对比./hello执行和之间hello执行

    ./hello可以正常运行helllo程序,而hello会显示没有找到该指令

  3. 为什么有些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行?

    以ls指令为例,ls有它的默认查找路径,在PATH环境变量中。环境变量也是变量(变量名PATH + 变量内容(路径))

    访问环境变量PATH:

    PATH是内置的变量,访问它要加$ ,使用echo访问:echo $PATH

    不同路径之间用 :分隔,所以以上4条路径就是我们服务器的默认查找路径,这些路径下的指令可以直接使用,而不用带路径名。所以执行命令就是在这些路径下先查找,找到了再执行

  4. 如果想像 ls一样直接执行 hello, 可以将我们的程序所在路径加入环境变量PATH当中:sudo cp hello /usr/local/bin

  5. 对比测试

    虽然这样可以实现直接执行命令,但是不推荐这样写,这样会污染Linux的命令池

  6. 还有什么方法可以不用带路径,直接就可以运行呢?

    可以将路径添加到环境变量里,重启xshell时又会生成新的默认路径,所以几乎不会对Linux造成危害:

    export PATH=$PATH:/home/ysj/practice/c-c/study_10-2-1

    export导出环境变量

  7. which的原理就是在默认路径下查找命令

HOME

HOME变量是保存当前用户的主工作目录

USER

显示当前登录用户

HISTSIZE

表示能记住的历史命令的最大条数

也就是当我们处于命令行时,可以用上下键翻阅最近使用的命令,最多向上翻阅HISTSIZE条

SHELL

显示充当shell的角色是谁

bash是shell的具体化,就像王婆与媒婆的关系

和环境变量相关的命令

  1. echo: 显示某个环境变量值
  2. export: 设置或修改一个环境变量
  3. env: 显示所有环境变量
  4. unset: 清除环境变量
  5. set: 显示本地定义的shell变量和环境变量

环境变量的组织方式

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\\0’结尾的环境字符串

通过代码如何获取环境变量

C如何获取环境变量呢?

我们常写的main函数是没有写参数的,实际上,main函数也是有参数的

int main(int argc, char *argv[], char *env[])

argc表示命令的个数,argv是字符数组,里面存着输入的命令,所以,argc和argv是帮我们收集命令行参数的。env同样也是字符指针数组,里面存着环境变量

我们在Linux中,输入ls -a -l,其中ls表示可执行程序,-a,-l表示命令行参数,对应着数组argv的内容

我们写一些代码来验证一下命令行参数的存在:

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

int main(int argc, char*argv[], char *env[])

    int i = 0;
    for (; i < argc; ++i)
    
      printf("argv[%d]:%s\\n ", i, argv[i]);
    
    return 0;


当我们不输入任何命令行参数时,默认只有./test命令

当我们输入-a -b -c选项时,就会把这些当成命令行参数放入数组中:

可以看到argv[0]是我们执行程序的指令 ./test, C/C++中命令行参数数组的第一个元素默认是执行程序的命令

获取环境变量

  • 命令行第三个参数

    env数组的最后一个元素为'\\0',遇到\\0退出循环

    #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;
    
    

    运行示例:

    获取的环境变量不止这些,这里只截取了一部分

  • 通过第三方变量environ获取

    #include &lt;stdio.h&gt;
    int main(int argc, char *argv[])
    
        extern char **environ;
        int i = 0;
        for(; environ[i]; i++)
        
        	printf(&quot;%s\\n&quot;, environ[i]);
        
    
        return 0;
    
    

    运行结果和上面一样

    libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用extern声明

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

函数原型:

char *getenv(const char *name);

参数是环境变量名,返回的是环境变量的内容,如果没有对应的环境变量,则返回\\0

使用:

#include <stdio.h>
#include <stdlib.h>
int main()

	printf("%s\\n", getenv("PATH");
	return 0;

程序运行的结果就是打印了默认地址

环境变量通常是具有全局属性的

  • 环境变量通常具有全局属性,可以被子进程继承下去

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    
        char * env = getenv("MYENV");
        if(env)
        
            printf("%s\\n", env);
        
        return 0;
    
    

    直接查看,发现没有结果,说明该环境变量根本不存在

  • 导出环境变量
    export MYENV="hello world"

  • 再次运行程序,发现结果有了。说明:环境变量是可以被子进程继承下去的(test是bash的子进程)。

    env中找MYENV:

如果只进行 MYENV=“helloworld” ,不调用export导出,在用我们的程序查看,会有什么结果?为什么?
使用unset删掉已经设置了的环境变量MYENV:unset MYENV

不调用export:

此时的MYENV不具有全局属性,所以要用export导出,使其具有全局属性,main里面才能访问到,最后打印出结果

程序地址空间

研究背景

kernel 2.6.32
32位平台

程序地址空间

在C语言中,为了理解,我们把程序的地址空间按如下区域进行认识:

可是我们对它并不理解

来段代码感受一下

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()

	pid_t id = fork();
	if(id < 0)
    
		perror("fork");
		return 0;
	
	else if(id == 0)//child
     
		printf("child[%d]: %d : %p\\n", getpid(), g_val, &g_val);
		
    else//parent
     
		printf("parent[%d]: %d : %p\\n", getpid(), g_val, &g_val);
	
	sleep(1);
	return 0;

输出

//与环境相关,观察现象即可
parent[11861]: 0 : 0x601058
child[11862]: 0 : 0x601058  

我们发现,输出出来的变量值和地址是一模一样的,这很好理解啊,因为子进程按照父进程为模版,父子并没有对变量进行进行任何修改。再将代码稍加改动 :子进程修改全局变量g_val

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()

	pid_t id = fork();
	if(id < 0)
  	
		perror("fork");
		return 0;
	
	else if(id == 0)//child,子进程肯定先跑,也就是子进程先修改,完成之后,父进程再读取
     
      while (1)
      
        g_val=100;
        printf("child[%d]: %d : %p\\n", getpid(), g_val, &g_val);
        sleep(1);
      
    
    else//parent
     
      while(1)
      
		    sleep(3);
		    printf("parent[%d]: %d : %p\\n", getpid(), g_val, &g_val);
	
      
    
    
    sleep(1);
    return 0;


输出结果:

//与环境相关,观察现象即可
child[3046]: 100 : 0x601058
child[3046]: 100 : 0x601058
child[3046]: 100 : 0x601058
parent[3045]: 0 : 0x601058

我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
  • 但地址值是一样的,说明,该地址绝对不是物理地址
  • 在Linux地址下,这种地址叫做 虚拟地址。地址空间,是对物理内存的一种虚拟化表示,虚拟空间最终会以某种方式转化到物理内存
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址。而物理地址用户一概看不到,由OS统一管理

OS负责将 虚拟地址 转化成 物理地址 。

子进程会继承父进程的数据,所以打印出来的地址是一样的,由于打印出来的地址是虚拟地址,而操作系统会将虚拟地址映射到物理地址上,所以实际上,父进程和子进程占有的物理地址空间是不同的,因此,g_val才会不同

由此也证明了,进程具有独立性。

进程地址空间

所以之前说‘程序的地址空间’是不准确的,准确的应该说成 进程地址空间 ,那该如何理解呢?看图

说明:
上面的图就足矣说明问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址

有多少个进程就有多少个进程地址空间(虚拟空间)

那么为什么需要虚拟空间呢?

如果只有物理内存,我们进程访问的地址都是物理地址,如果有野指针,就可能会将其他区域的数据修改,从而造成错误,而有了虚拟地址空间的话,野指针指向的地址在映射成物理地址后,如果这块空间不存在或者没有权限访问,程序就会报出错误,进而防止了一些内核的数据被修改。

而且,可能存在这种情况:

有两个进程的空间在物理内存上已经被分配好了,可是有可能在进程运行的时候,初始分配的空间不足,所以需要另外开辟空间,新开辟的空间此时就是不连续的。这样就使数据的访问变得不方便,并且增加了异常越界的概率

而有了虚拟地址:

这样就不会造成空间不连续,且可以保护内存。

管理地址空间

先描述

地址空间本质就是一个数据结构struct

struct mm_struct
unsigned long code_start;
unsigned long code_end;
unsigned long ...
    
...
...
    
;

申请空间的本质:向内存索要空间,得到物理地址,然后在特定区域申请没有被使用的虚拟地址,建立映射关系,返回虚拟地址即可。

再组织

task_struct中:

//mm:关于进程的地址空间,active_mm:指向进程的地址空间。(链表和红黑树)
struct mm_struct *mm, *active_mm;

task_struct通过这两个成员进行和mm_struct联系,每一个进程都会有唯一的mm_struct结构体。

进程重定义

进程是加载进内存的程序,由进程常见的数据结构(struct task_struct(控制块) 和 struct mm_struct(地址空间)) 和代码数据构成。

补充task_stuct中部分内容的解释

上下文数据:当一个进程在自己的时间片内运行时,当时间片结束,CPU的寄存器等存储器里还有属于当前进程的数据,这些数据被称为上下文数据,表示当前进程时间片终止时数据的处理情况,上下文数据要被保存进PCB(task_struct),否则到下次轮到该进程继续运行时,就不知道处理到哪一步了。然后下一个进程占用CPU继续运行

内存指针:可以找到进程的代码和数据。

程序计数器pc:保存当前正在执行指令的下一条指令的地址

运行队列:

等待队列:

以上是关于初始进程2.0的主要内容,如果未能解决你的问题,请参考以下文章

LINUX PID 1和SYSTEMD PID 0 是内核的一部分,主要用于内进换页,内核初始化的最后一步就是启动 init 进程。这个进程是系统的第一个进程,PID 为 1,又叫超级进程(代码片段

在 Python 多处理进程中运行较慢的 OpenCV 代码片段

Jekyll 偏移代码片段高亮的初始行

java 简单的代码片段,展示如何将javaagent附加到运行JVM进程

代码片段:Shell脚本实现重复执行和多进程

如何使用 Swift 使用此代码片段为 iOS 应用程序初始化 SDK?