公司动态
linux进程概念【万字解析,深度刨析linux底层系统】
目录一、冯诺依曼体系结构二、操作系统(Operator System)2.1 概念2.2 如何理解“管理”2.3 总结2.4 系统调用和库函数概念三、进程3.1 基本概念3.2 描述进程-PCB3.3 组织进程3.4 查看进程3.5 通过系统调用获取进程标识符3.6 通过系统调用创建进程-fork初识四、进程状态4.1 进程状态查看4.2 僵尸进程4.3 僵尸进程的危害4.4 孤儿进程五、进程优先级5.1 基本概念5.2 查看系统进程5.3 PRI and NI5.4 PRI vs NI5.5 用top命令更改已存在进程的nice5.6 其他概念5.6.1 进程切换六、环境变量6.1 基本概念6.2 常见环境变量6.3 查看环境变量方法6.4 测试PATH6.5 和环境变量相关的命令6.6 环境变量的组织方式6.7 通过代码如何获取环境变量6.7.1 命令行第三个参数6.7.2 通过第三方变量environ获取6.8 通过系统调用获取或设置环境变量6.9 环境变量是具有全局性的七、程序地址空间7.1 程序地址空间回顾7.2 进程地址空间7.2.1 理解虚拟地址空间7.2.2 进程地址空间7.2.3 为什么存在地址空间一、冯诺依曼体系结构我们常见的计算机如笔记本。我们不常见的计算机如服务器大部分都遵守冯诺依曼体系。截至目前我们所认识的计算机都是有一个个的硬件组件组成输入单元包括键盘, 鼠标扫描仪, 写板等中央处理器(CPU)含有运算器和控制器等输出单元显示器打印机等关于冯诺依曼必须强调几点这里的存储器指的是内存不考虑缓存情况这里的CPU运算器 控制器 其他 cpu能且只能对内存进行读写不能访问外设(输入或输出设备)外设(输入或输出设备)要输入或者输出数据也只能写入内存或者从内存中读取。一句话所有设备都只能直接和内存打交道。对冯诺依曼的理解不能停留在概念上要深入到对软件数据流理解上请解释从你登录上qq开始和某位朋友聊天开始数据的流动过程。从你打开窗口开始给他发消息到他的到消息之后的数据流动过程。如果是在qq上发送文件呢二、操作系统(Operator System)2.1 概念任何计算机系统都包含一个基本的程序集合称为操作系统(OS)。笼统的理解操作系统包括内核进程管理内存管理文件管理驱动管理其他程序例如函数库 shell程序等等2.2 如何理解“管理”管理的例子描述被管理对象组织被管理对象我们可以通过学校来类比操作系统这样校长就是管理者学生就是被管理者但是校长一般都是没见过或者不会刻意见学生但是校长是如何做管理的呢对管理者的理解管理者不需要与被管理者直接交互依旧能够把管理对象管理起来那是因为管理的本质是对数据的管理虽然学生不直接和校长打交道但是学生所有的数据早已经被校方拿走了而且一直在更新。但是学校学生这么多校长是怎么管理的呢先描述再组织—— 数据结构根据图上所描述的东西其实简单来说先描述指的就是先面向对象的把每位学生的信息通过结构体一 一列出来形成一个结构然后再组织的意思就是把每位学生的信息通过一个数据结构串起来保证以后能够方便的维护。2.3 总结计算机管理硬件描述起来用struct结构体组织起来用链表或其他高效的数据结构2.4 系统调用和库函数概念在开发角度操作系统对外会表现为一个整体但是会暴露自己的部分接口供上层开发使用这部分由操作系统提供的接口叫做系统调用。系统调用在使用上功能比较基础对用户的要求相对也比较高所以有心的开发者可以对部分系统调用进行适度封装从而形成库有了库就很有利于更上层用户或者开发者进行二次开发。根据上图可知一般我们使用操作系统的时候我们都是调用函数或者用指令操作我们并不知道里面的原理或者里面干了什么只能通过接口来和人交互。那是为什么呢其实和银行类似银行不相信任何人操作系统也不相信任何人因为操作系统和银行一样要保护自己不能让人随意访问但是也必须给上层用户提供各种服务。所以就有了类似银行的窗口一样操作系统有自己的接口函数或者指令操作。三、进程3.1 基本概念课本概念程序的一个执行实例正在执行的程序等内核观点担当分配系统资源CPU时间内存的实体3.2 描述进程-PCB进程信息被放在一个叫做进程控制块的数据结构中可以理解为进程属性的集合。课本上称之为PCBprocess control block Linux操作系统下的PCB是: task_struct实际上PCB就是对进程的描述里面装有该进程的所有属性。task_struct-PCB的一种在Linux中描述进程的结构体叫做task_struct。task_struct是Linux内核的一种数据结构它会被装载到RAM(内存)里并且包含着进程的信息task_ struct内容分类标示符: 描述本进程的唯一标示符用来区别其他进程。状态: 任务状态退出代码退出信号等。优先级: 相对于其他进程的优先级。程序计数器: 程序中即将被执行的下一条指令的地址。内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针上下文数据: 进程执行时处理器的寄存器中的数据[休学例子要加图CPU寄存器]。I O状态信息: 包括显示的I/O请求,分配给进程的I O设备和被进程使用的文件列表。记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。其他信息3.3 组织进程根据上图可以得知程序在磁盘加载到内存之后操作系统会描述对应的程序创建对应的结构体PCB多个程序的话就会在组织起来形成内核数据结构。那为什么会有PCBstruct task_struct 结构体呢那是因为操作系统一贯认为管理进程要先描述再组织先描述的话就要读取对应进程的属性那么就需要对应的PCB封装起来。3.4 查看进程大多数进程信息同样可以使用top和ps这些用户级工具来获取。示例代码#includestdio.h#includesys/types.h#includeunistd.hintmain(){while(1){sleep(1);}return0;}3.5 通过系统调用获取进程标识符进程idPID父进程idPPID示例代码#includestdio.h#includesys/types.h#includeunistd.hintmain(){printf(pid: %d\n,getpid());printf(ppid: %d\n,getppid());return0;}编译可以得出3.6 通过系统调用创建进程-fork初识fork有两个返回值父子进程代码共享 数据各自开辟空间私有一份采用写时拷贝示例代码#includestdio.h#includesys/types.h#includeunistd.hintmain(){intretfork();printf(hello proc : %d!, ret: %d\n,getpid(),ret);sleep(1);return0;}运行结果fork 之后通常要用 if 进行分流示例代码#includestdio.h#includesys/types.h#includeunistd.hintmain(){intretfork();if(ret0){perror(fork);return1;}elseif(ret0){//childprintf(I am child : %d!, ret: %d\n,getpid(),ret);}else{//fatherprintf(I am father : %d!, ret: %d\n,getpid(),ret);}sleep(1);return0;}运行结果四、进程状态进程状态一般有以下几种运行新建 就绪挂起阻塞等待 停止 挂机 死亡普遍的操作系统层面如何理解上面的概念什么叫运行状态运行状态不只是指正在运行的状态而是进程的PCB加载到运行队列时不管你是不是正在运行都叫运行状态。什么叫阻塞状态当一个进程需要访问外设时需要把这个进程的PCB从内存中的运行队列放到外设的等待队列中等访问过后该程序的PCB会回到运行队列里那么这个进程的PCB在等待队列时的状态叫做阻塞状态。什么叫挂起状态由上图的调度图和挂起流程可知当内存有多个进程是内存不够而有一个程序处于阻塞状态不会立即调度要等很长时间那么操作系统会把该进程的数据和代码暂时保存到磁盘上清理一些空间但是该进程的PCB还在内存里保证该进程存在那么该进程的PCB就处于挂起状态。如果该进程准备完毕操作系统会重新把该进程的数据和代码加载到内存。具体的linux操作系统的状态为了弄明白正在运行的进程是什么意思我们需要知道进程的不同状态。一个进程可以有几个状态在Linux内核里进程有时候也叫做任务。下面的状态在kernel源代码里定义/* * The task state array is a strange bitmap of * reasons to sleep. Thus running is zero, and * you can test for combinations of others with * simple bit tests. */staticconstchar*consttask_state_array[]{R (running),/* 0 */S (sleeping),/* 1 */D (disk sleep),/* 2 */T (stopped),/* 4 */t (tracing stop),/* 8 */X (dead),/* 16 */Z (zombie),/* 32 */};R运行状态running 并不意味着进程一定在运行中它表明进程要么是在运行中要么在运行队列里。S睡眠状态sleeping) 意味着进程在等待事件完成这里的睡眠有时候也叫做可中断睡眠interruptible sleep。D磁盘休眠状态Disk sleep有时候也叫不可中断睡眠状态uninterruptible sleep在这个状态的进程通常会等待IO的结束。T停止状态stopped 可以通过发送 SIGSTOP 信号给进程来停止T进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。X死亡状态dead这个状态只是一个返回状态你不会在任务列表里看到这个状态4.1 进程状态查看ps aux/ps axj 命令举例#includestdio.h#includesys/types.h#includeunistd.hintmain(){while(1){sleep(1);}return0;}通过查看该进程处于睡眠状态。4.2 僵尸进程僵死状态Zombies是一个比较特殊的状态。当进程退出并且父进程使用wait()系统调用没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。僵死进程会以终止状态保持在进程表中并且会一直在等待父进程读取退出状态代码。所以只要子进程退出父进程还在运行但父进程没有读取子进程状态子进程进入Z状态。示例代码1#includestdio.h2#includestdlib.h3#includeunistd.h4#includesys/types.h5intmain()6{7pid_tidfork();8if(id0)9{10while(1)11{12printf(子进程pid:%d,ppid:%d,id:%d\n,getpid(),getppid(),id);13sleep(1);14exit(1);15}16}17if(id0)18{19while(1)20{21printf(父进程pid:%d,ppid:%d,id:%d\n,getpid(),getppid(),id);22sleep(2);23}24}25else26{}27return0;28}运行结果为4.3 僵尸进程的危害进程的退出状态必须被维持下去因为他要告诉关心它的进程父进程你交给我的任务我办的怎么样了。可父进程如果一直不读取那子进程就一直处于Z状态是的维护退出状态本身就是要用数据维护也属于进程基本信息所以保存在task_struct(PCB)中换句话说 Z状态一直不退出 PCB一直都要维护是的那一个父进程创建了很多子进程就是不回收是不是就会造成内存资源的浪费是的因为数据结构对象本身就要占用内存想想C中定义一个结构体变量对象是要在内存的某个位置进行开辟空间内存泄漏?是的。4.4 孤儿进程父进程如果提前退出那么子进程后退出进入Z之后那该如何处理呢父进程先退出子进程就称之为“孤儿进程”孤儿进程被1号init进程领养当然要有init进程回收喽。示例代码1#includestdio.h2#includestdlib.h3#includeunistd.h4#includesys/types.h5intmain()6{7pid_tidfork();8if(id0)9{10while(1)11{12printf(子进程pid:%d,ppid:%d,id:%d\n,getpid(),getppid(),id);13sleep(1);14}15}16if(id0)17{18while(1)19{20printf(父进程pid:%d,ppid:%d,id:%d\n,getpid(),getppid(),id);21sleep(2);22exit(1);23}24}25else26{}27return0;28}运行结果五、进程优先级5.1 基本概念cpu资源分配的先后顺序就是指进程的优先权priority。优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用可以改善系统性能。还可以把进程运行到指定的CPU上这样一来把不重要的进程安排到某个CPU可以大大改善系统整体性能。5.2 查看系统进程我们很容易注意到其中的几个重要信息有下UID : 代表执行者的身份。PID : 代表这个进程的代号。PPID 代表这个进程是由哪个进程发展衍生而来的亦即父进程的代号PRI 代表这个进程可被执行的优先级其值越小越早被执行。NI 代表这个进程的nice值。5.3 PRI and NIPRI也还是比较好理解的即进程的优先级或者通俗点说就是程序被CPU执行的先后顺序此值越小进程的优先级别越高那NI呢?就是我们所要说的nice值了其表示进程可被执行的优先级的修正数值PRI值越小越快被执行那么加入nice值后将会使得PRI变为 PRI(new)PRI(old)nice这样当nice值为负值的时候那么该程序将会优先级值将变小即其优先级会变高则其越快被执行所以调整进程优先级在Linux下就是调整进程nice值nice其取值范围是-20至19一共40个级别。5.4 PRI vs NI需要强调一点的是进程的nice值不是进程的优先级他们不是一个概念但是进程nice值会影响到进程的优先级变化。可以理解nice值是进程优先级的修正修正数据5.5 用top命令更改已存在进程的nicetop进入top后按“r”–输入进程PID–输入nice值操作演示5.6 其他概念竞争性: 系统进程数目众多而CPU资源只有少量甚至1个所以进程之间是具有竞争属性的。为了高效完成任务更合理竞争相关资源便具有了优先级独立性: 多进程运行需要独享各种资源多进程运行期间互不干扰并行: 多个进程在多个CPU下分别同时进行运行这称之为并行并发: 多个进程在一个CPU下采用进程切换的方式在一段时间之内让多个进程都得以推进称之为并发5.6.1 进程切换由上图可知进程切换的意思就是操作系统运行程序的时候通过时间片在一段时间内运行一个程序下一段时间就运行另一个程序每个程序执行一个时间片的时间保证每个程序都有运行防止卡死在一个程序内。但是进程切换的时候进程执行到一部分的时候操作系统是怎么做的呢这和我们离开学校去当兵类似我们离开学校的时候学校会保留学籍回到学校的时候恢复学籍类似。进程也是进程切换后系统会进行上下文保护进程恢复的时候系统会进行上下文回复。ps:系统进行上下文保护的时候是保护的寄存器内的数据寄存器内的数据只属于当前运行的进程六、环境变量6.1 基本概念环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。如我们在编写C/C代码的时候在链接的时候从来不知道我们的所链接的动态静态库在哪里但是照样可以链接成功生成可执行程序原因就是有相关环境变量帮助编译器进行查找。环境变量通常具有某些特殊用途还有在系统当中通常具有全局特性。6.2 常见环境变量PATH : 指定命令的搜索路径HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)SHELL : 当前Shell,它的值通常是/bin/bash。6.3 查看环境变量方法echo $NAME//NAME:你的环境变量名称示例6.4 测试PATH创建hello.c文件#includestdio.hintmain(){printf(hello world!\n);return0;}将我们的程序所在路径加入环境变量PATH当中, export PATH$PATH:hello程序所在路径。输出结果6.5 和环境变量相关的命令echo: 显示某个环境变量值export: 设置一个新的环境变量env: 显示所有环境变量unset: 清除环境变量set: 显示本地定义的shell变量和环境变量6.6 环境变量的组织方式每个程序都会收到一张环境表环境表是一个字符指针数组每个指针指向一个以’\0’结尾的环境字符串。6.7 通过代码如何获取环境变量6.7.1 命令行第三个参数示例代码1#includestdio.h2#includestring.h3#includestdlib.h45#define USERUSER6#define MY_ENVmyval7#define MYPWDPWD8intmain(intargv,char*argc[],char*env[])9{10//NULL-011for(inti0;env[i];i)12{13printf(env[%d]:%s\n,i,env[i]);14}15return0;16}结果6.7.2 通过第三方变量environ获取示例代码#includestdio.hintmain(intargc,char*argv[]){externchar**environ;inti0;for(;environ[i];i){printf(%s\n,environ[i]);}return0;}结果libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。6.8 通过系统调用获取或设置环境变量getenv//获取环境变量示例1#includestdio.h2#includestring.h3#includestdlib.h45#define USERUSER6#define MY_ENVmyval7#define MYPWDPWD8intmain()9{10char*my_envgetenv(MYPWD);11printf(%s\n,my_env);12return0;13}结果为putenv//设置环境变量6.9 环境变量是具有全局性的环境变量通常具有全局属性可以被子进程继承下去示例1#includestdio.h2#includestring.h3#includestdlib.h45#define USERUSER6#define MY_ENVmyval7#define MYPWDPWD8intmain()9{10char*my_envgetenv(MY_ENV);11printf(%s\n,my_env);12return0;13}直接查看发现没有结果说明该环境变量根本不存在。导出环境变量export MYENV1234“再次运行程序发现结果有了说明环境变量是可以被子进程继承下去的想想为什么为了不同的应用场景 ------ 让bash帮我找指令路径身份认证七、程序地址空间7.1 程序地址空间回顾我们在讲C语言的时候老师给大家画过这样的空间布局图。来段代码感受一下。#includestdio.h#includeunistd.h#includestdlib.hintg_val0;intmain(){pid_tidfork();if(id0){perror(fork);return0;}elseif(id0){//child,子进程肯定先跑完也就是子进程先修改完成之后父进程再读取g_val100;printf(child[%d]: %d : %p\n,getpid(),g_val,g_val);}else{//parentsleep(3);printf(parent[%d]: %d : %p\n,getpid(),g_val,g_val);}sleep(1);return0;}输出结果为我们发现父子进程输出地址是一致的但是变量内容不一样能得出如下结论:变量内容不一样,所以父子进程输出的变量绝对不是同一个变量但地址值是一样的说明该地址绝对不是物理地址在Linux地址下这种地址叫做虚拟地址我们在用C/C语言所看到的地址全部都是虚拟地址物理地址用户一概看不到由OS统一管理。OS必须负责将虚拟地址转化成物理地址。7.2 进程地址空间所以之前说‘程序的地址空间’是不准确的准确的应该说成进程地址空间那该如何理解呢看图7.2.1 理解虚拟地址空间由上图可知我们可以把虚拟地址空间比作大饼每个员工都有一张饼并指向对应的员工每个员工的饼互不干扰。每个员工都要管理每个饼也要管理都是先描述在组织。7.2.2 进程地址空间如何理解区域划分对于区域划分类似于划分三八线一样每个部分都有一个区域和数学的区间一样。由上图可知每个进程task_struct都有一个地址空间每个地址空间有一个结构体struct mm_struct里面保存了每个区域的开始地址和结束地址。2. 区域调整也很简单两个区域假如每个都让5厘米那中间的10厘米就是缓冲区。如果其中一个区域需要更大的空间另一个区域可以让给这个区域让这个区域更大这就叫做区域扩大。3. 进程地址空间和内存的关联虚拟地址对物理地址的映射通过页表来完成通过下图得知进程1进程2分别是两个儿子他们分别都认为有4GB的的进程地址空间他们互不干扰他们只看得到自己的虚拟地址空间看不见真正的物理内存。7.2.3 为什么存在地址空间如果让进程之间直接访问物理内存万一进程越界非法操作呢非常不安全。所以页表对于非法操作会进行拦截。所有程序都要遵守。重新回到最初的问题为什么父子进程输出地址是一致的但是变量内容不一样呢由下图可知地址空间的存在可以更方便的进行进程和进程数据代码的解耦保证了进程独立性这样的特征。让进程以统一的视角来看待进程对应的代码和数据等各个区域方便编译器也以统一的视角来进行编译。由上图可知早在编译的时候内部的代码早就有了对应的虚拟地址然后虚拟地址空间的mm_struct结构体就会有对应代码的开始地址和结束地址。然后当你把代码加载到内存的时候他们也就具备了物理地址拥有两套地址加载完之后cpu就会把mm_struct的地址加载到他自己里面然后通过地址查页表通过页表映射到物理地址调用对应的函数。在访问的过程中cpu没有见到过物理地址。