初始进程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
当服务器执行一条命令时:
-
找到这条命令
-
运行它
创建hello.c文件
#include <stdio.h> int main() printf("hello world!\\n"); return 0;
对比./hello执行和之间hello执行
./hello可以正常运行helllo程序,而hello会显示没有找到该指令
为什么有些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行?
以ls指令为例,ls有它的默认查找路径,在PATH环境变量中。环境变量也是变量(变量名PATH + 变量内容(路径))
访问环境变量PATH:
PATH是内置的变量,访问它要加
$
,使用echo访问:echo $PATH
不同路径之间用
:
分隔,所以以上4条路径就是我们服务器的默认查找路径,这些路径下的指令可以直接使用,而不用带路径名。所以执行命令就是在这些路径下先查找,找到了再执行如果想像
ls
一样直接执行hello
, 可以将我们的程序所在路径加入环境变量PATH当中:sudo cp hello /usr/local/bin
对比测试
虽然这样可以实现直接执行命令,但是不推荐这样写,这样会污染Linux的命令池
还有什么方法可以不用带路径,直接就可以运行呢?
可以将路径添加到环境变量里,重启xshell时又会生成新的默认路径,所以几乎不会对Linux造成危害:
export PATH=$PATH:/home/ysj/practice/c-c/study_10-2-1
export导出环境变量
which的原理就是在默认路径下查找命令
HOME
HOME变量是保存当前用户的主工作目录
USER
显示当前登录用户
HISTSIZE
表示能记住的历史命令的最大条数
也就是当我们处于命令行时,可以用上下键翻阅最近使用的命令,最多向上翻阅HISTSIZE条
SHELL
显示充当shell的角色是谁
bash是shell的具体化,就像王婆与媒婆的关系
和环境变量相关的命令
- echo: 显示某个环境变量值
- export: 设置或修改一个环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- 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 <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;
运行结果和上面一样
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 代码片段