当前位置: 首页 > news >正文

网站使用前流程汉中市建设工程信息申报系统

网站使用前流程,汉中市建设工程信息申报系统,公司怎么建网站做推广,做网站是域名怎么申请~~~~ 前言冯诺依曼体系结构#xff08;重要#xff09;总览CPU工作方式什么是指令集#xff1f;CPU为什么只和内存打交道#xff08;数据交换#xff09;#xff1f;木桶效应#xff1a;在数据层面的结论程序运行为什么要加载到内存#xff1f; 进一步理解计算机体系结… ~~~~ 前言冯诺依曼体系结构重要总览CPU工作方式什么是指令集CPU为什么只和内存打交道数据交换木桶效应在数据层面的结论程序运行为什么要加载到内存 进一步理解计算机体系结构 操作系统operator system重要什么是操作系统为什么要有操作系统操作系统怎样进行管理的先描述在组织。管理的进一步理解 系统调用和库函数操作系统体系结构 进程进程概念重要初步理解进程概念进一步理解进程概念进程控制块PCB见见进程见见猪跑查看进程信息 ps axj杀掉进程见见与进程相关的系统调用另一种查看进程的方式ls /proc了解进程与程序的区别与联系正式认识进程获取进程id进程id如何变化的创建子进程fork 进程状态初步理解进程状态概念运行状态概念重点阻塞状态概念重点挂起状态概念重点什么是计算密集型什么是IO密集型Linux的进程状态是怎么实现的运行状态Rrunning睡眠状态S浅度睡眠阻塞状态的一种暂停状态Tstopped挂起状态用户看不到追踪的停止状态ttracing stop死亡状态 X深度睡眠状态(D)了解不是重点但是理解了对操作系统本身很重要僵尸状态zombie 孤儿进程什么是孤儿进程 进程优先级了解什么叫做优先级为什么存在优先级Linux优先级特点 -- 很快怎样表示优先级查看优先级ps -la使用top设置进程优先级nir 进程切换 环境变量什么是环境变量应用场景PATH让我们写的程序也像系统指令一样可以直接执行方法1echo命令方法2export命令 导入环境变量 环境变量的全局属性的体现set命令unset命令看看windows系统的环境变量 使用getenv库函数获取指定环境变量USER一个关于权限的例子 环境变量的组织方式字符指针数组C语言main函数的命令行参数char* argv[] 命令行参数表char* env[] 环境变量表environ获取环境变量表 echo问题待整理 程序地址空间回顾C/C地址空间见见现象fork 修改了但没完全修改 感性理解虚拟地址空间 进程虚拟地址空间什么是地址空间如何理解地址空间进一步理解地址空间 拓展结语 前言 本文将会介绍Linux操作系统进程相关的概念。 推荐书籍《深入理解计算机系统》 冯诺依曼体系结构重要 冯诺依曼体系结构计算机的基本原理 计算机就是为了完成指定的数据处理而通过指令按指定流程完成指定功能指令的合集就是一段程序。计算机就是按照指令执行流程完成对指定数据的处理。 总览 存储器指内存特点是掉电易失。 外存除内存之外的具有永久性存储能力的介质如磁盘、u盘光盘等。 外设相对于内存和CPU包括运算器、控制器、寄存器等而言其他设备都是外围设备。 输入设备典型的输入设备就是鼠标和键盘了。 输出设备如显示器。 输入输出设备如磁盘、网卡等。 设备名用途速度量级CPU进行计算很快纳秒级存储器内存临时存储较快微秒级外设外围设备永久存储较慢毫秒级、秒级 CPU工作方式 CPU是很笨的只会只能被动接受别人程序给它的指令并进行执行和计算但是CPU执行速度非常非常快。 CPU为什么能认识别人的指令呢其实是因为CPU存在着指令集只要在指令集内的指令CPU都认识。 什么是指令集 包括精简指令集和复杂指令集。 指令集是CPU中用来计算和控制计算机系统的一套指令的集合。 就像一个人想要听懂其他语言如法语就需要先学习法语的语法记忆法语的单词形成自己的‘指令集’然后才能够认识法语文章听懂法语内容。而CPU在设计之初就已经在硬件层面先天有了自己的一套指令集编译器对我们的源代码进行编译并形成二进制可执行程序这个可执行程序内就包含了一条条能够被CPU认识的指令和数据。 CPU受到指令周期的控制而一直读取指令一刻也不会停下来就像人一出生就被时间、社会推着跑小学、初中、高中、大学、工作、结婚…停不下来。 CPU为什么只和内存打交道数据交换 木桶效应 即木桶能装多少的水不是由最长的木板决定的而是由最短的木板决定的。CPU处理速度非常非常快外设速度通常非常慢CPU直接和外设打交道会严重拖慢CPU的速度。所以CPU只和速度较快的内存理解为大大的缓存打交道虽然也慢但是相比外设速度来说已经可以接受了。 CPU直接和内存打交道进行数据交换目的是为了提高效率。 在数据层面的结论 CPU直接和内存打交道不和外设打交道所有的外设数据需要载入时只能先载入到内存中CPU处理完的数据再经过内存写入到外设中 程序运行为什么要加载到内存 程序运行时要加载到内存使我们很早就知道的概念但是在学习操作系统之前也仅限于知道而不晓得为什么。 在了解了计算机的体系结构之后我们就能够知道这是冯诺依曼体系机构的规定 CPU读取指令和处理的数据都直接从内存中读取而程序在外设磁盘中CPU无法直接访问磁盘不能从磁盘读取程序指令需要程序先加载到内存然后CPU读取指令并执行程序。。 进一步理解计算机体系结构 各种软件必须依托于冯诺依曼体系结构设计即硬件决定软件。软件的底层也天然的依托于体系结构万变不离其宗体系结构。 冯诺依曼体系结构是底层设计相当于骨架软硬件依据计算机体系结构就像肌肉组织依托于骨架。 栗子理解 登陆同一聊天程序qq时A向B发送一条信息hello B!“到B在自己电脑上看到信息为止数据流是如何进行流动的 A通过外设键盘输入信息hello B”数据由键盘发送到A电脑的内存中数据被CPU处理之后再回到到A电脑的内存中A按下发送键后数据从内存发送到了A电脑的网卡和显示器内A电脑的网卡又经过网络传输发送给B电脑的网卡内再从B电脑网卡发送到B电脑的内存中数据在经B电脑CPU处理再发送给内存然后再由内存发送到B显示器上被B所看见。 A向B发送一个文件时数据流又是如何流动的 文件事先被保存在外设磁盘中文件先由磁盘流到A电脑内存在流到CPU被处理之后流回内存然后再由内存发送到A电脑的网卡内经过网络传输A电脑网卡把文件发送给了B电脑网卡B电脑网卡再把文件流到内存中然后CPU对文件处理之后再流回内存然后再由内存发送到B电脑的外设磁盘内进行保存发送到外设显示器内进行数据显示。 操作系统operator system重要 CPU快速但机械的执行一条条的指令并处理数据那么CPU要读取哪一个进程的哪一条指令从哪里读取处理完的数据如何刷新哪里磁盘、显示器 关于如何进行决策的问题这就需要操作系统进行统一管理了。 什么是操作系统 操作系统核心功能是进行软硬件资源管理的软件。 包括进程管理、文件系统、内存管理、驱动管理等。 主要学习进程管理、文件系统、一点点内存管理、了解驱动管理。 我们主要学计算机生命周期中使用时间最久的部分进程、文件操作系统非常复杂源码达到了千万行级别一个人穷极一生去学习也不可能学得完也不需要这样学。我们要了解和学习的是操作系统的整体结构和主要的部分。 为什么要有操作系统 操作系统为什么需要进行管理 对下通过合理的管理软硬件资源手段对上为用户提供良好的稳定的、安全的、高效的执行环境目的。 操作系统怎样进行管理的 先描述在组织。 例子在学校中校长是管理者学生是被管理者。但是校长并不会直接对具体的某一个学生进行直接管理那么校长是如何知道学生的具体的情况呢又是如何进行管理的呢 校长虽然没有直接和所有学生直接接触和交流但是校方想要掌握的学生的信息都被持续收集了起来比如各科期末成绩基本信息、正在读哪个年级等虽然管理者和被管理者没有进行直接接触但是管理者通过对被管理者的各种数据的管理就可以把被管理者给管理起来。 什么是管理呢就是面对重大事件时具有决策的权力。而管理者想要做出适当的决策是需要知道被管理者各种数据信息作为支撑的没有数据管理者对被管理者什么都不了解也就无法进行有效的管理。 管理的本质是对数据进行管理。 如果只有管理者和被管理者那么被管理者的数据是如何被拿到的呢所以还有一个执行者角色如各种驱动程序负责执行管理者做的决策搜集被管理者的各种数据。 用户使用者 管理者操作系统做决策依据被管理者的数据 执行者驱动程序每个硬件都会有对应的驱动程序否则硬件如鼠标将无法正常使用执行管理者的命令、和被管理者直接接触持续获取对应的数据 被管理者硬件、软件 在对真实场景中的对象进行管理时我们通常都需要先考虑用结构体或类把我们需要记录的对象信息抽象归纳为对应的变量就是描述起来然后再考虑使用哪一种或哪几种数据结构把一个个的对象结构体给有效的组织起来。 管理的进一步理解 现在校长操作系统作为管理者通过执行者驱动程序不断搜集的学生的各种数据被管理者对学生进行管理。可是一个学校可能会有几千人几万人学生产生的各种数据也就会非常多。校长直接面对海量的数据表格进行管理不太现实需要进行分门别类的处理。学生被管理者的数据虽然是众多的但是对于整个学生群体来说数据主要包含了学生的姓名、学号、班级、身份证号、电话号码等等。 操作系统面对被管理者海量的数据时为了有效的对被管理者进行管理这些数据本身也需要被管理起来。 类似于学生产生的数据操作系统会先把学生的共同特征进行描述建立学生结构体类型struct student这个结构体类型里面就包含了一个学生的基本信息的集合 struct student{name;number;class;id;telphone;...struct student* next; };这样一个学生的所有信息用一个结构体保存和管理结构体内有一个自身类型的结构体指针于是对于多个学生结构体便可以以链表的形式把所有学生结构体链接起来。这样操作系统想找到某一个学生的信息时只需要遍历这个结构体链表即可。这样对学生数据的管理就转化为了对学生结构体链表进行管理增删查改了。 // 以链表结构组织学生信息 struct student *head (struct student*)malloc(sizeof(struct student)); struct student *stu1 (struct student*)malloc(sizeof(struct student)); stu1-name xx; stu1-number xxx; ... head-next stu1;先描述即把学生数据对象共同特征抽取出来进行封装用结构体表示 在组织对学生结构体struct student采取适当的数据结构组织起来如链表、队列、二叉树。 操作系统对外设磁盘硬件的数据本身进行管理方式也是先描述 // 定义设备结构体类型 struct dev{type;类型status;//状态... };在组织: // 以链表形式组织 struct list_node{struct dev devs;struct list* next; }list_node* head_dev (listNode*)malloc(sizeof(listNode)); init_list(head_dev);//初始化链表头结点 list_node* disk_dev (listNode*)malloc(sizeof(listNode)); insert(head_dev, disk_dev);//节点插入链表 list_node* keyboard_dev (listNode*)malloc(sizeof(listNode)); insert(head_dev, keyboard_dev);上述代码描述只是一个形象化的描述是为了表示操作系统对数据是怎样进行管理的。 操作系统作为一款软件既能管理各种硬件也能管理各种软件。就像人一样人可以管理桌椅板凳、也可以管理人本身。管理软件的方式也是对软件的数据本身进行管理依然要经历先描述在组织的过程。 系统调用和库函数 操作系统最为计算机的管理者位置实在是太重要了操作系统只要出了一点问题计算机可能就会无法正常使用。所以操作系统不允许其他任何人、任何程序直接访问和修改操作系统内核而是通过提过系统接口的形式经过操作系统来进行各种操作如读文件、写磁盘、写显示器等。 操作系统不相信除自己之外的任何人但是操作系统又必须给上层用户程序提供各种服务操作系统不允许上层用户程序直接访问操作系统内的数据而是对外提供了一系列的系统接口函数用户程序通过这些系统接口函数来间接访问让操作系统自己访问数据结果返回给用户程序操作系统内的数据。 Linux操作系统是由纯C语言写的所以其系统调用都是以C语言接口函数使用C语言的编码方式的形式为上层用户提供的。 操作系统为上层用户程序提供的系统调用对于我们来说并不好用但是我们用户程序只能使用操作系统提供的难用的众多系统调用。于是在系统调用之上又出现了一层对系统调用进行再封装的程序隐去了难用的系统调用为我们提供方便的操作如shell外壳、编程语言官方提供的丰富的库函数、以及一些其他的命令。 相比于直接使用系统调用用户操作接口的出现大大方便了我们用户的日常使用如编程开发、快捷命令、管理操作等。 系统调用把应用程序的请求传输给系统内核执行 利用系统调用能够得到操作系统提供的多种服务用户只需要将自己的请求以及数据通过系统调用接口传递给内核内核中完成对应的设备访问过程最终返回结果 系统调用是操作系统向上层提供的用于访问内核特定功能的接口 系统调用给用户屏蔽了设备访问的细节 系统调用保护了一些只能在内核模式执行的操作指令系统向上层提供系统调用接口用于访问内核服务或功能的很大原因也是因为这样可以最大限度的保护内核的稳定运行 系统调用的运行过程是在内核态完成的操作系统并不允许用户直接访问内核也就是说用户运行态并不满足访问内核的权限。 操作系统体系结构 进程 进程概念重要 初步理解进程概念 课本概念 一个运行起来的程序就是进程在内存中的程序就是进程进程和程序相比具有动态属性进程是程序运行的一个实例虽然并没有说错但也只是只是了读了之后没啥感觉也不知道有啥用。 进一步理解进程概念 想要理解进程就不能局限于进程本身我们需要深入到进程运行的背后理解进程运行背后的一整套逻辑链 进程是操作系统分配各种资源CPU、内存、磁盘等的实体。 内存中有许多已加载的程序这么多的程序操作系统如何进行管理呢如何为这些进程分配CPU等资源呢 进程控制块PCB 操作系统不会直接对某一个进程进行直接管理而是把所有进程的数据抽象出公共属性作为一个结构体称之为进程控制块PCB通过对PCB的管理间接的对所有进程进行管理进程的所有信息都被保存在了PCB(process control block)中。 在Linux中描述进程的结构体是task_struct是Linux内核的一种数据结构记录进程的各种信息。 task_struct内都有什么 标示符: 描述进程的唯一标示符用来区别其他进程。状态: 任务状态退出代码退出信号等。优先级: 相对于其他进程的优先级。程序计数器: 程序中即将被执行的下一条指令的地址。内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针。上下文数据: 进程执行时处理器的寄存器中的数据。I/O状态信息: 包括I/O请求,分配给进程的I/O设备和被进程使用的文件列表。记账信息: 包括处理器时间总和使用的时钟数总和时间限制记账号等。其他信息。 对进程要有先描述再组织的认识 **进程 ** 内核数据结构(task_struct) 进程对应的磁盘代码 为什么会有PCB(struct task_struct)结构体呢 操作系统是对软硬件资源进行管理的**管理的本质是对数据进行管理**。而众多数据杂乱无章数据太多时数据本身就是一个负担还需要对收集的数据本身进行管理借助面向对象的思想把进程相关的数据进行抽象归纳出公共属性以结构体的形式进行描述。 管中窥豹可见一斑见见Linux(2.6版本)的PCB源码 对PCB即struct task_struct的定义非常复杂虽然我们现在还看不懂但是我们知道Linux操作系统把进程的各种信息都定义了一个结构体进行保存一个这样的结构体对象就可以代表一个进程。对进程的管理就转变为了对进程PCB的管理。 见见进程见见猪跑 #includestdio.h #includeunistd.h int main(){ while(1){ printf(我是一个进程!\n); sleep(1); } return 0; }运行图 查看进程信息 ps axj 显示Linux系统中所有进程信息 ps axj 或ps aux我们来看看刚才进程的运行状态 head -1表示保留前一行 grep myproc1表示在进程信息中搜索出现myproc1的进程并显示 杀掉进程 kill -9 进程名见见与进程相关的系统调用 返回当前进程的id pid_t getpid() 头文件是unistd.h和sys/types.h #includestdio.h #includeunistd.h #includesys/types.h int main(){ while(1){ printf(我是一个进程! 我的pid是 %d\n, getpid()); sleep(1); }return 0; }另一种查看进程的方式ls /proc了解 在**/proc目录下有着与进程PID一一对应的目录**这些目录名就是进程名目录里存放的就是进程所有的信息。你没看错在Linux下一个进程被当成了文件看待。 /proc/21593目录下的exe指向了我的家目录下的源程序路径。 这个目录里面有很多进程相关的信息以文件或目录的形式存在我们虽然不知道这些是哪些信息但是其中一个信息exe我们是一看就知道的它指向了进程的可执行程序的路径即说进程是从磁盘的那个路径下的哪个可执行程序加载而来的。 当一个进程被创建时Linux操作系统除了会为进程创建对应的PCB还会在/proc目录下生成一个与进程PID同名的目录如果进程结束运行或意外终止除了内存中的进程控制块、加载的代码和数据被清理掉外这个目录也会自动被操作系统删除。 进程与程序的区别与联系 在进程被创建后如果磁盘上对应的可执行程序被删掉那么进程还能够正常运行吗 可执行程序被删掉之后进程在/proc对应目录下的exe文件所指的路径变为了闪烁的红色并在结尾用deleted提示我们当前进程在磁盘上的可执行程序被删掉了。 答案是能。进程被创建后可执行程序的代码和数据都已经被加载到了内存进程运行时CPU直接读取进程在内存对应的指令即可。进程是程序的一个副本或者说一个实例。 正式认识进程 获取进程id getpid() 获取当前进程id getppid() 获取当前进程的父进程id提示当系统调用或库函数对应头文件不知道时使用man # command查看#号文档的command文档。 进程id如何变化的 多次重复 运行再结束命令行上的进程的过程我们会发现进程的pid在发生变化这是理所应当的因为进程每次创建数据都会重新加载到内存但是其父进程id始终没有变化 我们发现在命令行上直接运行的进程其父进程都是bash即都是以bash的子进程的方式运行的。这样的好处是就算进程运行出1 现异常了也不会影响到父进程bash实习生出现问题了不影响公司。 编译并运行./myproc3 假如myproc因为空指针访问而崩溃了看看bash会不会崩溃 杀掉bash例子命令行不能正常响应 创建子进程fork 所需头文件unistd.h pid_t fork(void); fork创建子进程之后父进程和子进程共享fork之后的所有代码。 fork之后如果子进程创建成功向父进程返回子进程PID向子进程返回0如果创建失败返回-1并设置错误码。 图片-》 #includestdio.h #includeunistd.h int main(){ pid_t id fork(); (void)id; while(1){ printf(pid: %d, ppid: %d\n, getpid(), getppid()); sleep(1); } return 0; }即一个函数(fork)竟然有了两个返回值你是否觉得十分神奇与不可理解究竟是怎么回事呢为什么会有两个返回值呢 难道我们C语言功底出现问题了一个函数不是只能有一个返回值吗 其实这并不是C语言语法本身能够解释的问题也不代表我们C语言功底不行这只是因为这是系统调用函数fork。 以前C语言中同一段代码的if和else只能执行一个即程序只会进入一个if或者else执行代码死循环但是现实情况是fork之后即进入if执行了死循环也进入了else执行了死循环非常的难以理解对吧 这就是涉及到多进程概念了。 即fork之后的代码被父进程和子进程共享有父进程子进程两个进程执行后续所有代码 根据fork返回值的不同让父进程和子进程分别执行后续代码的一部分这就是并发的概念。 注意以后会见到很多类似fork的与多个进程相关的代码所以进程必须要深入学习和理解这是以后接触到频率很高最高的部分也是基础中的基础。 进程状态 想要知道正在运行的进程是什么意思我们需要先知道进程的不同状态。 初步理解进程状态概念 操作系统的课本中一般会提到的进程概念有 运行、新建、就绪、挂起、阻塞、等待、停止、挂机、死亡等等。 在普遍的操作系统层面理解上面的进程各种状态概念 进程这么多的状态本质都是为了满足不同的运行场景。 运行状态概念重点 对于一台计算机来说一般只有一个CPU同一时刻也只能执行一个进程的指令和数据。但是内存中有那么多的进程到底先让CPU执行这一个进程的指令还是先执行那一个进程的指令呢操作系统是怎样做调度的呢 通过一个运行队列把一个个进程对应的进程控制块PCB放入这个队列然后CPU从队列头开始按照固定时间间隔通过PCB找到对应的进程依次执行对应进程的指令和数据。 运行队列runqueuue保存着内存中一个个进程的PCB 运行队列形象描述 //PCB形象描述 struct task_struct{pid;ppid;state;//进程其他对应属性struct task_struct*next; }; //运行队列形象描述 struct runqueue{task_struct*head;task_struct*tail;...//其他属性 };处于运行队列的进程PCB表示进程处于运行状态但是并不一定正在被CPU读取和执行指令。 因为运行队列中有很多进程PCBCPU以时间片轮转的方式依次执行处于运行队列的进程对应的指令所以运行状态的进程也可能在等待时间片轮转并没有被CPU执行指令。 与投简历过程做类比 网上面试流程一般是应聘者制作自己的简历把电子简历投递到意向公司然后公司对简历进行一定的筛选然后对通过的简历整理排序安排面试时间。 其中应聘者自己就相当于加载到内存的进程制作的简历就相当于描述进程各种信息的进程控制块PCB简历中包含了应聘者的基本信息和技术栈、项目经历、实习经历等PCB中类似的包含了进程pid进程身份证等。应聘者把简历投递到意向公司的邮箱相当于进程的PCB加入到了运行队列中。公司看中你的简历了想约你的面试就通过简历上你的联系方式通知你参加面试。相当于操作系统选择某个进程进入CPU执行了就通过运行队列里该进程PCB里的指针找到进程的指令和数据在内存中位置并进行读取和执行。 小结 一个CPU有限的资源对应一个运行队列。让进程进入队列本质就是经该进程的task_struct结构体放入运行队列中。进程PCB只要在运行队列中那么就是运行状态R)而不是只有进程在运行时才是运行状态state。进程不止会等待或占用CPU资源其他资源如磁盘、显示器、网卡等也会随时被进程占用或访问。进程所谓的各种状态不要把它给完全抽象的理解它在PCB中的具体实现其实就是一个整型变量简单和具体。 阻塞状态概念重点 进程除了会等待占用CPU资源也会等待外设资源磁盘、网卡、显示器等因为外设资源相比于进程来说数量也很少。 一个进程访问外设时其他进程也想访问外设时应该怎么办呢 操作系统会分别创建这些外设资源建立各自的等待队列多个进程需要访问外设且外设如磁盘正在被占用操作系统就把进程的PCB从运行队列放入了对应外设磁盘的等待队列PCB内进程状态信息由运行状态变成阻塞状态。这样CPU就不用因为进程需要等待外设资源时自己也随着等待了转而去执行运行队列中的其他进程CPU在运行队列里依次高速的执行每一个进程。等到外设磁盘资源空闲时操作系统再把进程的PCB从等待队列放入运行队列等待CPU继续执行该进程。 所谓的进程不同的状态本质就是进程在不同的队列中等待某种资源运行队列也是在等待CPU资源。 所谓进程等待某种资源我们宏观的感受就是这个软件为什么这么卡下载进度为什么一直不动 挂起状态概念重点 处于阻塞状态的进程一直在等待相关资源空闲意味着阻塞状态的进程短期内不会被CPU立即调度也就意味着该进程将会等待一段时间不会被CPU执行。 如果内存中进程太多导致内存空间不足时操作系统为了保证计算机能够继续正常运行会把处于阻塞状态且相当长的一段时间内未被调度的进程的代码和数据从内存写入到磁盘的特定区域进行保存。把该进程的代码和数据所占的空间释放了出来供其他进程使用但是该进程的PCB仍然在内存中保存这就是挂起状态。直到该进程等待的资源空闲且内存有足够的空间时操作系统再把该进程对应的代码和数据再次加载到内存对应PCB从挂起队列放入阻塞队列再由阻塞队列放入运行队列进程状态也从挂起状态变成阻塞状态在从阻塞状态变成运行状态。 一个进程阻塞不一定挂起但挂起一定已经阻塞了。 这种将进程的相关数据加载或写入到磁盘的过程称之为内存数据的换入换出。 操作系统的调度对每一个进程来说都是公平的相对公平不可能一直让同一个进程一直占用资源而让其他进程一直等待。 对进程来说我的阻塞或挂起都是为了其他进程的运行那么我运行的时候其他进程也必然会为了我进行相应的阻塞或挂起。有行有等有张有弛这就是操作系统调度的规则。 挂起状态不止会与阻塞状态进行组合只要不是运行状态都可能会被操作系统变成对应状态的挂起状态。如就绪挂起进程刚加载到内存还没有运行就由于内存空间不足又被换出到磁盘在内存只剩下对应的PCB了。 什么是计算密集型什么是IO密集型 简单来说计算密集型主要进行各种计算更多的是需求CPU资源所以更多更可能的处于运行状态。如a一直计算。 IO密集型就是更多的进行数据从内存到外设磁盘的换入和换出更多的处于阻塞状态睡眠S暂停T。如printf一直写入显示器。 Linux的进程状态是怎么实现的 课本上介绍进程概念时只是笼统、抽象的进行介绍不针对一种具体的操作系统为了不保证错误也不会讲的很具体。 关于进程状态不同的操作系统具体的实现并不完全一样。下面介绍Linux操作系统的进程状态具体是如何设计与实现的 /* * 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. */ static const char * const task_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 */ };操作系统书上的概念是对进程各种状态的抽象是一种概念便于我们进行理解和学习而已。 具体的一款操作系统比如Linux操作系统进程并没有所谓的新建状态、就绪状态、阻塞状态、挂起状态等倒是有运行状态®有睡眠状态(S)、停止状态(T)和追踪停止状态(T)、深度睡眠状态(D)有僵尸状态(Z)和死亡状态(X)等。 R运行状态running : 并不意味着进程一定在运行中它表明进程要么是在运行中要么在运行队列 里。 S睡眠状态sleeping): 意味着进程在等待事件完成这里的睡眠也叫做可中断睡眠interruptible sleep。 D磁盘休眠状态Disk sleep也叫不可中断睡眠状态uninterruptible sleep在这个状态的 进程通常会等待IO的结束。 T停止状态stopped 可以通过发送 SIGSTOP 信号给进程来停止T进程这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。 X死亡状态dead这个状态只是一个返回状态在任务列表里看不到这个状态 。 运行状态Rrunning 见见运行状态Rwhile(1)死循环 睡眠状态S浅度睡眠阻塞状态的一种 见见睡眠状态Sprintf printf向显示器打印数据会访问外设而CPU速度相比于显示器是非常快的所以每次访问外设显示器时可能100次有99次显示器都在被占用CPU执行进程的一条写入指令后需要等待显示器空闲的时间相比执行的时间很长进程便进入了显示器的阻塞队列。CPU则继续执行其他进程直等到显示器空闲CPU才再次执行该进程的下一条写入指令。 所以用户查看进程的状态时总是看到该进程处于阻塞状态S而几乎看不到该进程处于运行状态R待写入的数据从CPU执行写入指令开始到被显示器显示的期间CPU执行指令花费的时间占比可能只有1%R状态而其他99%的时间都是数据被写入显示器的时间S状态。 当使用ps axj | head -1 ps axj | grep myproc查询进程myproc的运行状态时也会查询到grep命令本身。 暂停状态Tstopped 暂停状态是阻塞状态的一种。 了解命令 kill -19 进程暂停运行 kill -18 进程继续运行 同时我们注意到Linux的进程状态后面默认带着号而当我们暂停进程之后进程状态变成了T而不是T即号被去掉了。我们恢复进程的运行后进程状态变成了R而不是R。结论带着号的进程是命令行前台进程不带号的是命令行后台进程。 前台进程和后台进程 前台进程运行在shell命令行上在进程运行期间不接受用户输入的大部分命令和操作除非等前台进程自己退出或用户ctrlc强制结束进程状态后会有一个号进行标识。 图片-》 后台进程一个进程运行起来默认就是前台进程在使用kill -19暂停之后进程就变成了后台进程进程状态不带号然后再使用kill -18继续进程的运行我们发现该进程运行时我们在命令行输入的命令可以被命令行执行按ctrl c也无法终止该进程了。此时需要输入命令 kill -9杀掉该进程。 挂起状态用户看不到 Linux操作系统中用户无法直接看到挂起状态操作系统没有暴露出该状态给用户。即操作系统不希望用户知道一个进程此时是不是被挂起了也没有必要知道是否被挂起了这是操作系统应该要考虑的事和用户没关系。 追踪的停止状态ttracing stop 被追踪的停止状态。 对程序myproc4进行调试 使用gdb等调试工具对进程调试运行时进程就进入了tracing stop状态也是一种暂停状态以便调试器可以读取和修改进程的状态和内存。 死亡状态 X 进程处于死亡状态时操作系统会延迟的对该进程进行回收但是这个延迟对于我们用户来说也是很快的所以我们无法查看到死亡状态。 深度睡眠状态(D)了解不是重点但是理解了对操作系统本身很重要 先说浅度睡眠就是上文所说的睡眠状态(S)和停止状态(T/t)。浅度睡眠是可以被通过kill -9被操作系统杀死的。 再来看深度睡眠Disk sleep 深度睡眠出现的场景进程在等待进行大量的I/O操作的完成进程会进入D状态在大量I/O操作完成后才会切换到其他状态。因为进程的数据通过操作系统向磁盘I/O时进程需要关注I/O操作是成功了还是失败了如果失败需要向用户报告I/O失败或者重新向磁盘I/O需要保证进程一直存在不能被操作系统为了内存管理而被随便kill -9掉防止出现磁盘数据写入失败相关进程又被操作系统意外kill -9掉用户的数据没被保存丢了。 处于深度睡眠的进程无法被操作系统通过信号唤醒和杀死操作系统只能等待该进程自己醒来。 当一台机器的进程出现大量的D状态时想要解决就只能重启机器甚至需要进行断电。 僵尸状态zombie 僵尸进程子进程先于父进程退出父进程没有对子进程的退出进行处理因此子进程会保存自己的退出信息而无法释放所有资源成为僵尸进程导致资源泄露。 见见僵尸进程 例子子进程运行5秒后退出父进程一直不退出也不对子进程进行回收看看父进程和子进程分别是什么什么状态 #includestdio.h #includeunistd.h #includesys/types.h int main(){ pid_t id fork(); if(id 0){ // child int cnt 5; while(cnt){ printf(我是子进程! pid: %d, %d秒后退出\n, getpid(), cnt); sleep(1); cnt--; } } else{ while(1){ // parent nothing to do printf(我是父进程! pid: %d\n, getpid()); sleep(1); } } return 0; } 为什么要有僵尸状态(先初步理解深入理解在进程控制部分引入) 我们知道进程被创建出来是为了完成某一种或几种任务的。进程最后完成任务的情况是怎样的是完成并成功了、是完成但失败了、还是异常终止了操作系统或者其父进程需要知道完成的结果所以进程终止时不能直接释放进程对应的资源而是保存一段时间以便让父进程或操作系统读取之后再被父进程或操作系统释放其对应的资源。 在进程终止之后一直到进程的退出信息被父进程或操作系统读取完成的时间段内都是僵尸状态之后该进程才由僵尸状态变为死亡状态各种相关的资源不久就依次被操作系统回收。 僵尸进程无法被kill -9杀掉 因为僵尸进程已经死亡了你无法杀死一个已经死掉的进程。 死亡状态的进程也无法被kill -9杀掉原理同上。 僵尸进程危害 僵尸进程可能存在内存泄漏的问题 僵尸进程的退出信息被保存在了PCB中一个进程退出了代码和数据可以被直接释放但是对应的其他资源理解为PCB资源这个词太抽象了让人不好理解不会被直接释放而是等待父进程或操作系统读取退出信息并由父进程或操作系统回收该进程。如果父进程一只不回收僵尸进程那么僵尸进程的PCB就会一直保存在内存中PCB相比代码和数据不大但是也是一个复杂的大型结构体变量一直在内存中占用空间内存中可用空间就变少了这不就是内存泄漏吗。 进程的退出状态必须被维持下去因为他要告诉关心它的进程父进程你交给我的任务我办的怎 么样了。可父进程如果一直不读取那么子进程就一直处于Z状态。 维护退出状态本身就是要用数据维护也属于进程基本信息所以保存在task_struct(PCB)中换句话 说 Z状态一直不退出 PCB一直都要维护。 如果一个父进程创建了很多子进程就是不回收就会造成内存资源的浪费。因为数据结构对象本身就要占用内存想想C语言中定义一个结构体变量对象是要在内存的某个位置进行开辟空间的。 如何避免内存泄漏呢 孤儿进程 孤儿进程父进程先于子进程退出子进程成为孤儿进程运行在后台父进程成为1号进程而孤儿进程的退出会被1号进程负责任的进行处理因此不会成为僵尸进程。 孤儿进程的产生一般都会带有目的性比如我们需要一个程序运行在后台或者不想一个进程退出后成为僵尸进程。 什么是孤儿进程 一个父进程fork了一个子进程之后先于子进程退出了。 本来父进程要读取子进程的退出信息对子进程进行资源PCB的回收的但是现在子进程还在正常运行但父进程先退出了。 例子: #includestdio.h #includeunistd.h #includesys/types.h int main(){ pid_t id fork(); if(id 0){ // child while(1){ printf(我是子进程! pid: %d, ppid: %d\n, getpid(), getppid()); sleep(1);} } else{int cnt 5;while(cnt){ printf(我是父进程! pid: %d, %d秒后退出!\n, getpid(), cnt); sleep(1);cnt--; } }return 0; }运行截图 现象1父进程退出后子进程继续运行且ppid变成了1 现象2父进程退出后子进程进程状态的号被取消了变成了一个后台进程。 父进程被kill -9后子进程的PPID有原先父进程的PID变成了1那么编号为1的是哪一个进程呢原来是操作系统 像这样父进程退出子进程继续运行的子进程将会被操作系统接管领养称之为孤儿进程。 并且父进程先于子进程退出将会导致子进程由前台进程变为后台进程只能通过kill -9杀掉。 进程运行中会出现父进程先退出的情况吗 父进程退出后也会被它的父进程进行回收操作。 答案一定是存在父进程先退出的情况结果就是子进程被操作系统领养子进程ppid变为1该进程就被称之为孤儿进程为什么操作系统要领养孤儿进程因为操作系统要进行管理如果操作系统不领养孤儿进程那么孤儿进程就退出后进入僵尸状态就没有人来回收了父进程先退出了肯定不考虑操作系统也没有接收。如果是前台进程创建的子进程如果前台进程先退出那么子进程在被操作系统领养变成孤儿进程的同时该子进程还会自动变成后台进程。 进程优先级了解 优先级其实就是进程怎样进行排队的问题。 什么叫做优先级 CPU调度中进程被调度执行的顺序。 与权限区分考虑的是能不能做有没有资格做的问题 优先级能做有资格做考虑的是怎样排队确定谁先谁后的问题 为什么存在优先级 僧多粥少狼多肉少。 进程多资源少CPU、各种外设等。 Linux优先级特点 – 很快 怎样表示优先级 优先级本质就是PCB内的整型数字。 用两个数字共同构成了优先级pri(priority)、ni(nice) 最终优先级 老优先级(默认80) ni;优先级默认值80 查看优先级 ps -la 使用top设置进程优先级nir Linux支持进程运行中对优先级的调整而调整的策略就是通过更改nice值来实现的。 具体过程 运行myproc1查看myproc1的优先级。 sudo top进入资源管理器 按r后上方出现输入提示首先输入要修改的进程pid 之后设置要修改的优先级ni为-100 查看修改后的myproc1的优先级 发现即使优先级ni想要设置为-100但是操作系统限制ni最低为-20。 下文也会发现优先级ni想要设置为100但是操作系统限制ni最高为19。 设置下面是设置优先级ni为100 优先级数字越小表示进程的优先级越高类似于排名。 nice 取值范围[-20, 19] 进程优先级取值默认就是80老的优先级固定是80与nice加和之后得到最终的优先级范围是[60, 99] 比如原优先级是90需要的新优先级是70所以设置nice为-10而不是-20。 虽然用户可以对进程的优先级进行设置但是操作系统不会随便让用户设置不合理的优先级这与操作系统的调有关如果允许用户为一个进程设置很高的优先级优先级越高的进程越先执行意味着其抢占CPU资源的能力越高。可能会引起操作系统的调度失衡。 虽然操作系统不能保证进程调度是绝对公平的但是要保证进程调度是相对公平的操作系统还是能做到的。 进程具有竞争性 操作系统中进程很多而CPU等资源是少量的所以进程之间具有竞争属性。为了高效完成任务合理竞争相关资源就有了进程优先级的概念。 进程具有独立性 例子子进程崩溃退出之后变成僵尸状态等待父进程回收而父进程并不受子进程崩溃的影响正常运行。 #includestdio.h #includeunistd.h #includesys/types.h int main(){ pid_t id fork(); if(id 0){ // child printf(我是子进程! pid: %d, ppid: %d\n, getpid(), getppid()); sleep(1); int * p NULL; *p 10; } else{ while(1){ printf(我是父进程! pid: %d\n, getpid()); sleep(1); } } return 0; }并行多个进程在多个CPU下分别同时运行。 并发同一时间间隔内多个任务或进程、线程在CPU中同时执行。 进程切换 CPU是计算机中的一个硬件CPU内有除了有运算器和控制器还有着大量的存储容量小但高速存储部件通常用于暂存指令、数据称之为寄存器。CPU中的寄存器包括向用户暴露出来的以供用户程序使用还包括对用户隐藏的供操作系统使用。 寄存器的工作方式取指令、分析指令、执行指令三种。指令指的就是由操作系统自身提供或用户编写的代码并被编译器翻译而成的二进制。 为什么需要进程切换 因为每个进程的运行时间是不同的有的进程一瞬间就运行完了而有的进程需要长时间运行不会很快结束甚至是我们写的死循环。进程运行时间有长有短CPU也不可能逮着一个进程一直运行而让其他进程一直等待而是每个进程执行一段时间再转而执行下一个进程循环往复。 一个进程执行时对应的指令和数据会被CPU读取在CPU内部的寄存器保存着当前进程执行产生的各种临时数据当CPU时间片轮转需要执行下一个进程时CPU内寄存器包含的进程各种临时数据需要被保存起来供下一次程序执行继续当前的进度。 PCB内是保存CPU的寄存器内各种临时数据上下文的地方。当CPU时间片轮转进行进程切换时操作系统会先把该进程在CPU内运行产生的各种临时数据保存在特定的位置上下文保护当再次轮到相同的进程执行时操作系统再把保存的上一次产生的各种临时数据一一恢复到CPU对应寄存器中上下文恢复然后进程就可以继续依据上一次的数据继续运行。 即进程在切换时要进行进程的上下文保护 进程恢复运行时要进行上下文的恢复 上下文指的是CPU内寄存器内的数据而不是指CPU内的寄存器。 在任何时刻CPU中寄存器内的数据看起来是在大家都能看到的寄存器上但是寄存器里的数据只属于当前运行的进程。即寄存器硬件被所有进程共享而寄存器里的数据则是每个进程各自私有的称之为上下文数据。 上下文的保存和恢复是非常快的。 环境变量 什么是环境变量 操作系统在启动命令行解释器的时候为我们预先设置好的供不同场景下使用的一系列的全局变量其实就是一个字符串称之为环境变量。 env查看所有环境变量。 应用场景 PATH 指定命令的搜索路径。 为什么我们的可执行程序执行需要带./路径而系统命令工具就不需要带/路径直接使用名字就能够执行呢 首先我们要先明白一点程序执行是需要代码和数据被加载到内存的。操作系统想要执行程序需要先找到这个程序在哪里吧。系统命令都放在了系统目录/usr/bin目录下而/usr/bin在环境变量PATH中再执行系统命令时操作系统会自动在对应搜索路径下搜索不需要指定路径。而我们的程序默认在自己创建的路径下程序所在目录不在PATH中且程序本身也不再系统目录/usr/bin下所以操作系统找不到我们的程序也就没办法执行只有指定了路径时才能被找到在被执行。 基本概念 系统命令为什么运行时不需要带./路径呢而我们自己写的可执行程序在运行的时候就需要带上./路径 栗子我们的程序不带./时运行出错 要执行一个程序首先要先找到这个程序。 我们如果想让我们写的程序也像系统指令一样不需要./就能直接执行有哪些方法呢 让我们写的程序也像系统指令一样可以直接执行 方法1 把我们的程序拷贝到系统默认目录下之后再执行我们的程序就不用在带./了。 但是并不推荐这么做因为我们所写的程序没有经过系统性的测试不知道会出现什么问题。 什么是安装起始就是把文件从一个地方拷贝到另一个地方。 为什么在系统默认路径下的程序就能被直接找到呢直接使用程序名就能执行呢 这是因为Linux系统中存在一个环境变量PATHPATH中记录了一系列的路径以:分隔。 系统指令ls、pwd、touch等都在/usr/bin目录下而/usr/bin又被PATH记录这样执行系统指令时操作系统就通过PATH在/usr/bin目录下找到指令对应的程序所以指令不带./就可以执行了。 echo命令 使用echo $查看、获取环境变量PATH echo $环境变量名方法2 export命令 导入环境变量 export错误示范 所以如果环境变量PATH被清空或覆盖了那么系统指令就都不能直接执行了 不要担心PATH被清空了系统命令都用不了怎么办因为当前设置的PATH是内存级的只需要重新登陆系统PATH就会恢复。 export如果直接设置环境变量PATH为对应路径会把就PATH覆盖掉正确做法是把需要添加的新路径追加到旧PATH中 export PATH$PATH:对应路径which指令是如何找到系统其他指令的位置呢通过上面的例子我们就知道了which就是通过环境变量PATH来进行查找的。这也解释了为什么当我们的程序所在目录在加入了PATH中后which就能找到我们的程序所在位置了。 我么在写C/C的时候我们对定义变量是十分熟悉的但是你知道吗我们的命令行也就是bash也是可以定义变量的 例子 但是我们定义的环境变量使用echo命令能找到但是使用env命令显示系统所有环境变量却找不到 因为我们定义的变量默认是本地变量属于局部变量。 尝试使用getenv()函数进行获取 #includestdio.h #includestdlib.h #includestring.h #define USER USER #define MY_ENV myval int main(){ const char* myval getenv(MY_ENV); printf(%s not found\n, MY_ENV); return -1; printf(%s%s\n, MY_ENV, myval);return 0; }意料之中的结果找不到本地变量myval 把myval导入export环境变量 export myval此时使用env就可以找到我们定义的myval了 再次运行程序myproc1getenv()也能找到我们定义的myval了 环境变量的全局属性的体现 bash就是一个系统进程mproc1通过fork的方式以bash子进程的方式运行。环境变量是操作系统为bash进程定义的并且可以被子进程继承下去这就是环境变量具有全局属性的原因这样环境变量就可以使用在不同的场景中如方便寻找路径、进行身份验证等。 本地变量是什么呢其实本地变量就是在当前进程就是bash定义的变量所以也只在当前进程有效。 可以类比为C语言中的全局变量可以被所有代码块访问而局部变量定义在函数体或其他局部域中只能在局部访问。 栗子ls我们知道是显示指定目录下的文件和目录不指定时默认显示当前路径下文件和目录。那么ls进程能够知道用户当前所在路径的原因就是因为ls进程以bash子进程的方式运行继承了bash所有的环境变量而环境变量中的PWD就保存了用户当前所在的路径ls通过getenv获取PWD就知道了用户所在的路径。 简单实现pwd命令 #includestdio.h #includestdlib.h #define PWD PWD int main(){ printf(%s\n, getenv(PWD)); return 0; }运行图片 hostname 显示主机名原理也是通过getenv获取环境变量HOSTNAME然后显示在屏幕上。 set命令 既显示环境变量又显示本地定义的shell变量。 unset命令 清除环境变量 看看windows系统的环境变量 我们普通用户在登陆bash时.bash_profile和.bashrc就会执行加载环境变量的操作。 什么是环境变量这种由操作系统提供的具有全局属性的变量。 环境变量不只有PATH每一种环境变量都有其特定的功能。 HOME环境变量记录用户的家目录所以我们cd ~时的~就被解释为HOME HOSTNAME主机名 LOGNAME当前登录的用户名 HISTSIZE记录的历史命令的最大条数 环境变量PWD会随着用户所在目录的变化而变化这样用户使用pwd命令时通过PWD环境变量就可以知道用户所在目录了。 例子 su或su-之后USER前后的变化 当前用户是weihe环境变量USER的值是weihe。 使用su切换为root用户身份不会重新登陆依然是原先用户的环境 su使用su -切换为root并重新登陆登录环境是root的 su -使用getenv库函数获取指定环境变量USER 环境变量USER记录着当前使用Linux的用户名 头文件 stdlib.h char *getenv(const char *name);一个关于权限的例子 我们在程序内部获取当前Linux用户并进行判断只有是root用户才能进行操作其他用户提示权限不足 #includestdio.h #includestdlib.h #includestring.h #define USER USER int main(){ const char* who getenv(USER); if(strcmp(who, root) 0){ // 执行操作之前先进行身份验证 printf(USER%s\n,who); printf(USER%s\n,who); printf(USER%s\n,who); printf(USER%s\n,who); } else{ printf(权限不足!\n); } return 0; } 图片 su -sudo系统命令执行时也会进行一系列的身份验证其中重要的一环就是获取当前用户身份并进行判断如果没有权限命令就不会被执行。 环境变量的组织方式字符指针数组 C语言main函数的命令行参数 char* argv[] 命令行参数表 #includestdio.h int main(int argc, char* argv[]){ return 0; }这里的char* argv[]是char*类型的指针数组指向了一个个的字符串。接收的是操作系统或父进程传入的参数。 先来看看char* argv[]里有什么 #includestdio.h int main(int argc, char* argv[]){ for(int i 0; i argc; i){ printf(argc[%d]%s\n, i, argv[i]); } return 0; }命令行参数的意义根据不同的命令行参数选项让同一个程序执行不同的命令。 #includestdio.h #includestring.h //ls -a -b -c -d int main(int argc, char* argv[]){ if(argc ! 2){ printf(Usage:\n\t%s\n, [-a/-b/-c/-ab/-ac/-bc/-abc]); return 1; } if(strcmp(argv[1], -a) 0){ printf(功能a!\n); } if(strcmp(argv[1], -b) 0){ printf(功能b!\n); } if(strcmp(argv[1], -c) 0){ printf(功能c!\n); } if(strcmp(argv[1], -ab) 0){ printf(功能ab!\n); } if(strcmp(argv[1], -ac) 0){ printf(功能ac!\n); } if(strcmp(argv[1], -bc) 0){ printf(功能bc!\n); } return 0; }windows下命令行参数 关机栗子 设置在360秒之后关闭计算机 取消关闭计算机 char* env[] 环境变量表 main函数形参除了argc、argv之外还有一个char* env[]这个env结构与char* argv[]一样env内部的指针储存着环境变量字符串的起始地址。 每个程序都会收到一张环境表环境表是一个字符指针数组每个指针指向一个以\0为结尾的字符串。 #includestdio.h int main(int argc, char* argv[], char* env[]){ for(int i 0; env[i]; i){ printf(env[%d]%s\n, i, env[i]); } return 0; }environ获取环境变量表 environ是C语言库函数unistd.h中定义的二级字符指针指向了字符指针数组该字符指针数组的指针依次指向了环境变量对应的字符串。 #includeunistd.h extren char** environ;使用environ打印环境变量 #includestdio.h #includeunistd.h int main(){ extern char** environ; for(int i 0; environ[i]; i){ printf(environ[%d]%s\n, i, environ[i]); } return 0; }echo问题待整理 程序地址空间 回顾C/C地址空间 这里的地址空间是内存吗 答案是不是内存。 不是内存那这是什么呢这是虚拟地址空间 见见现象fork 修改了但没完全修改 两个进程修改同一个全局变量global_val #includestdio.h #includeunistd.h int global_val 100; int main(){ pid_t id fork(); if(id 0){ printf(fork error\n); return 1; } else if(id 0){ int cnt 6; while(1){ printf(我是子进程! pid: %d, ppid: %d | global_val: %d, global_val: %p\n, getpid(), getppid(), global_val, global_val); sleep(1); if(cnt 0){ global_val 200; printf(我是子进程! global_val已经被我修改了!!!!!\n); } cnt--; } } else{ while(1){ printf(我是父进程! pid: %d, ppid: %d | global_val: %d, global_val: %p\n, getpid(), getppid(), global_val, global_val); sleep(2); } } return 0; }这里子进程把global_val的值修改但是地址没有变首先这个地址一定不是物理地址我们在C/C语言中学习到的地址或者指针也不是物理地址。这个地址叫做虚拟地址线性地址、逻辑地址。 感性理解虚拟地址空间 进程会认为自己独占系统资源彼此之间不知道其他进程的存在我们知道实际上不是这样的。 这体现在什么地方呢就是进程地址空间。 32位下操作系统给每个进程都许诺了4GB的内存空间蓝图每当进程需要申请空间时都会找操作系统要申请空间并且一个进程多数时候要申请空间一般是一点一点的申请少量的申请空间不会一下子把4GB空间一下子申请完即使想申请很大的空间如2GB甚至更大操作系统也不会给所以每个进程虽然有着4GB虚拟空间的“大饼”但是实际上每个进程只是申请了自己需要的少量空间。这样每个进程都被“4GB”内存空间的“大饼”给忽悠着继续运行操作系统也悠哉的为每个进程分配实际的空间相安无事。 我么知道了进程地址空间其实就是操作系统给进程画的“饼”。但是内存中同时运行的进程非常多操作系统需要给每个进程“画饼”进程地址空间那么“饼”进程地址空间多了之后操作系统对“饼”进程地址空间本身也需要进程统一管理不然万一画错了饼进程可就不干了出问题了。 那么依据“先描述在组织”的思想我们把进程地址空间这张“饼”本身给管理起来就要抽象出公共属性建立结构体struct。 虚拟地址空间的本质是内核的一种数据结构mm_struct 虚拟地址空间按照字节为单位进行划分 32位下CPU可寻址2^32个地址 可表示的空间范围是(*2^32)1Byte 4GB 每一个字节都有一个唯一的地址 对地址空间进行编址从低地址到高地址为0x00000000到0xffffffff 什么是区域划分 进程地址空间是以字节为单位的逻辑上连续的空间在进程地址空间中按功能被划分成了多个不同多区域。对于连续的空间不同功能区域的划分其实只要标记出每个区域的起始和结束位置即可。 struct mm_struct{uint32_t code_start, code_end;//代码区起始和结束uint32_t data_start, data_end;//数据区起始和结束uint32_t heap_start, headp_end;//堆区起始和结束uint32_t stack_start, stack_end;//栈区起始和结束...// };如何进行区域调整扩大、缩小 对于一个进程来说其代码区和数据区起始是确定大小的一般起始确定就不会再更改。而对于堆区和栈区涉及到空间的动态变化不过我们已经定义了每个区域的起始start和结束end那么堆区和栈区的缩小和扩大在虚拟地址空间中只需要更改结束位置end的值就可以实现。 区域起始地址与区域结束地址之间的所有地址就是该区域的虚拟地址。 堆区heap和栈区stack的区域动态调整扩大或缩小堆区或栈区本质就是通过修改各个区域的end或start实现的。 如定义局部变量或函数调用创建函数栈帧扩大栈区、malloc申请堆空间扩大堆区 而除了作用域局部变量销毁或被调函数返回栈帧销毁缩小栈区free掉申请的空间缩小堆区。 32位操作系统给每一个进程画的大饼 - 进程地址空间的大小是4GB。 证明看看Linux源码mm_struct 进程虚拟地址空间 什么是地址空间 进程地址空间表示的只是一段范围并不储存进程的数据和代码数据和代码储存在物理内存上。 引入页表 页表是用于对进程地址空间中的虚拟地址线性地址和物理内存中的物理地址进行映射建立对应关系。每一个进程除了有自己的PCB结构体、进程地址空间结构体还会有自己的页表对应的结构体由操作系统进行统一管理。 物理内存被分成了4KB大小为单位的page页这样4GB的物理内存就被分成了4GB/4KB即2^20个page内存页。 线性地址在C语言中我们知道一维数组在内存中是连续存储的二维数组在内存中也是连续存储的即二维数组是由一维数组组成的二维数组的地址也是连续、线性的我们可以使用既可以使用两层循环遍历二维数组也可以使用一层循环遍历二维数组。 首先认为Linux中虚拟地址和线性地址是同一个概念。 如何理解地址空间 为什么存在进程地址空间 如果让进程直接访问物理内存万一进程越界非法操作该怎么办呢这非常不安全。地址空间的存在可以更方便的进行进程和进程的数据、代码的解耦保证了进程的独立性。 进程直接访问物理内存容易出问题确实不行但是进程地址空间为什么就行呢 页表的作用不止是把虚拟地址映射到物理地址 页表还会对进程访问或读取的虚拟地址进行判断如果进程涉及到非法访问物理内存就会直接进行拦截进程被操作系统处理 进程地址空间保护了物理内存保证了进程特别是恶意进程不能随便访问物理内存。 让进程以统一的视角来看待其对应的代码和数据等各个区域方便使用编译器也以统一的视角对代码进行编译。二者采用相同的规则编译完就能直接使用。 对开始例子的底层解释 进一步理解地址空间 我们写的可执行程序里面在没有被加载到内存的时候就有内部逻辑地址了。不只是操作系统会遵守虚拟地址空间的规则编译器在编译我们的代码时也会遵守。 编译器在编译我们的代码时就是按照虚拟地址空间的方式对我们的代码和数据进行编址的。 像这样由编译器按照虚拟地址空间的方式进行编址就形成了可执行程序内部的地址一般称为逻辑地址等价于虚拟地址。当程序被加载到物理内存中时程序的代码和数据就天然具有了物理地址外部在程序内部有着编译时形成的内部地址如被调函数地址肯定是相对于内部来说的而不是相对于外部物理地址。 拓展 僵尸进程指的是进程退出后不会完全释放资源会造成系统资源泄漏 孤儿进程在父进程退出后父进程成为init进程进程退出孤儿进程的资源将被init进程释放 操作系统通过pcb实现对程序运行调度控制 fork系统调用通过复制父进程创建一个子进程父子进程数据独有代码共享在数据不发生改变的情况下父子进程资源指向同一块物理内存空间调研写时拷贝技术 在抢占式多任务处理中进程被抢占时哪些运行环境需要被保存下来 所有cpu寄存器的内容cpu上正在处理的数据。 页表指针 程序切换时会将页表起始地址加载到寄存器中。 程序计数器 下一步程序要执行的指令地址。 程序是静态的指令集合保存在程序文件中 进程是程序的一次运行过程中的描述。 作业是用户需要计算机完成的某项任务是要求计算机所做工作的集合。 一个程序可以同时运行多次也就有了多个进程。一个作业任务的完成可由多个进程组成且必须至少由一个进程组成程序是静态的而进程是动态的。 进程是操作系统对于程序运行过程的描述而这个描述叫做进程控制块-PCB它是操作系统操作系统管理以及调度控制程序运行的唯一实体。 因为进程ID只是进程的标识符是系统能够找到特定进程的标识而已。 进程管理器只是对大量PCB进行管理的一个程序而已。 进程本质上来说没有名字它有所调度管理运行的程序的名称它的标识是进程ID可以把进程ID当做是它的名字。 在系统角度看来进程是对于程序运行的描述就是PCB进程控制块。 syslogd系统中的日志服务进程 initinit进程是内核启动的第一个用户级进程用于完成处理孤儿进程以及其他的一些重要任务 sshd远程登录服务进程 vhand内存置换服务进程 守护进程精灵进程是同一种特殊的孤儿进程不但运行在后台最主要的是脱离了与终端和登录会话的所有联系默默的运行在后台不想受到任何影响。 结语 本文从冯诺依曼体系结构开始介绍引入了操作系统的体系结构和操作系统管理的本质。着重介绍了进程概念通过程序从磁盘加载到内存操作系统为其建立PCB、进程地址空间、页表对应数据结构来进行描述。特别是进程地址空间这是理解进程概念的关键参悟了进程地址空间也就理解了进程。
http://www.zqtcl.cn/news/181170/

相关文章:

  • 怎么样让百度搜到自己的网站wordpress的短代码
  • 聊城专业网站建设公司电子商务网站建设与维护李建忠下载
  • icp备案网站接入信息怎么写长兴县网站建设
  • 如何在网上注册公司网站网站不想让百度收录
  • 服务器做jsp网站教程视频免费的舆情网站app下载
  • 肇庆网站建设方案优化家居定制类网站建设
  • 自助建站加盟备案的网站有什么好处
  • 科技公司企业网站建设重庆seo优化
  • 空间站天宫vr全景尚层装饰
  • 有没有专门做中考卷子的网站网络公司推广公司
  • 网站建设费用如何列支wordpress页面构建
  • 用dw做网站怎么做出下拉菜单企业进行网站建设的方式有( )
  • 纯静态网站索引怎么做如何用wampp 做网站
  • 怎样做网站吸引人wordpress数据可视化插件
  • 网站运营管理教材中国设计之窗官方网站
  • 高端网站设计高端网站制作P2P网站怎么建设
  • 一般网站建设的流程故事app怎么制作
  • 一般在什么网站上做电子请帖国外产品设计网
  • 成都网站建设987netADPR国际传媒网站建设
  • 网站开发培训光山价格低
  • 营销型企业网站诊断网站开发图片侵权
  • 电商货源网站大全HTML网站页面建设
  • 购物网站建设款流程html博客转wordpress
  • 泉州建设培训中心网站大连云购物app下载安装到手机
  • 美食网站建设策划书帮人恶意点击网站
  • 网站项目合同永久免费的网站软件
  • 门户网站有哪些局限性wordpress 登录信息
  • 某网站项目策划书怎么做一个简单的网站
  • 建设网站 翻译黑色网站配色
  • 企网官方网站建筑工程网上备案流程