前言
在了解进程概念之前,还有很多东西需要我们先了解,以助我们更好地学习以后的知识
大部分都是概念的东西,大部分大概有个印象就好了
冯诺依曼体系结构(硬件方面)
当代计算机的设计都是按照冯诺依曼体系结构设计(大概结构为下图)的
那么它是有什么魔力呢?可以让那么多的计算机都按照这个结构设计
首先我们要知道,cpu处理数据的能力是最快的,内存次之,外设(磁盘等)最慢
真因此价格也是从高到低的
一台电脑从理论上来说可以全部由cpu组成,因为几乎每个硬件都有存储数据的能力,但是那样计算机的价格就太高了
如果要使计算机被广泛推广并使用,一定要价格合适且效率不错,冯诺依曼体系结构就做到了这点
我们首先要了解一下木桶效应,简单的来说就是一同水的容量多少取决于最小的那块木板
所以如果cpu直接和外设交互,就会导致整机效率过低
一般都是外设的数据加载到内存中,cpu与内存交互并处理数据,然后再返回给内存,再传到输出设备中去
有了以上的了解,我们就能明白为什么以前总是听说程序在运行前要被加载到内存中去
因为程序=代码+数据,本质上就是一个文件,被存储到磁盘中,想被cpu处理就要被加载到内存中
粗谈操作系统
什么是操作系统?
简单来说操作系统就是进行软硬资源管理,并为用户提供安全,稳定,高效的使用环境的一款软件(内存中第一个加载的软件)
我们先来看下三段(操作系统,驱动程序,硬件设备)之间是怎么进行资源管理的
操作系统,驱动程序,硬件设备可以形象地理解为校长,辅导员,学生
如果直接由校长直接管理学生那校长的负担会很大,但是如果只用管理几个辅导员,由辅导员去管理学生,校长的工作量就可以骤减了
先抛出一个结论:管理的本质不是管理一个人,而是管理数据!!!
校长原本愁与管理学生,但是学了编程以后创建了struct结构体,里面包含学生的各种属性,然后还有指针将每个结构体相连成链表,这样对学生的管理就变成了对链表的增删查改
管理方法:先描述(属性赋值),再组织(数据结构)!!!
人是通过属性认识世界的;重要的属性的集合就代表这个人
谈完了下三层,我们再来看上三层(为用户提供高效稳定安全的使用环境)
用户有很多,有的可能是普通用户,有的可能是开发者,有的可能是破坏者
所以操作系统为了安全的环境,实行一刀切政策,不信任任何用户,任何用户只能通过系统调用来使用操作系统
这和银行很像,银行不信任任何客户但是仍然为我们提供服务,所以设置了防弹玻璃保护自己,设置了一个小窗口(系统调用)用来处理业务
但是系统调用的学习成本太高,不利于用户使用,站在开发者的角度上,可以将系统调用封装成各种函数接口,但是一个函数由不同的人来写就有不同的函数接口,所以开发者将函数接口封装成各种各样好用的函数,也就是库,以后的开发者就不用自己再写了,直接调用即可
注意:想访问底层的os数据或者硬件,一定要贯穿整个层状结构
进程
进程和程序很容易混淆,进程是在内存中的程序吗?当然不是!
进程=可执行程序+内核数据结构(PCB)
进程在内存中,操作系统理所应当也要对其进行管理,怎么管理呢?先描述再组织!
描述就是各种属性加上拷贝可执行程序的代码,组织就是将其组织成链表结构
当然一个进程的pcb可以不止在一张链表(一个等待队列中)里,也可以在其他的数据结构中
这样对进程的管理,就变成了对链表的增删查改——本质就是一个建模的过程
我们在Linux中平时敲的指令以及各种运行的程序都是进程!!!!
查看进程
每个进程都要有自己的工作目录
进程的信息可以通过 /proc /进程id 来查看
getpid()——获取进程id
getppid()——获取父进程id
也可以通过ps(ps axj)和top这种用户级别软件来查看(一般配合grep指令和管道进行操作)
grep指令:grep 文件名——找出含有文件名名字的文件
grep -v 文件名——找出不含有文件名的文件
通过系统调用fork创建进程
在外出旅行的时候我们应该试过在车上一边下载视频一边播放视频以打发时间,这里就是两个进程在同时工作,也反映了创建子进程的原因——让子进程去做和父进程不一样的事,以更高地服务我们
格式:pid_t fork()
在fork后一般用if进行分流,fork会给父进程返回子进程的pid,给子进程返回0
接着我们抛出几个问题,下面再一一解释
1.fork干了什么
2.为什么fork有两个返回值
3.为什么fork的两个返回值,会给父进程返回子进程id,给子进程返回0
4.fork之后,父子进程谁先运行
5.如何理解同一个变量却有不同的值(待解决...)
首先有了上面进程的概念,fork创建子进程后,子进程是没有代码和数据的,系统会以父进程的pcb为模板拷贝一份pcb出来,这时父子进程共享代码。
但是如果子进程要修改数据的话也会影响到父进程的数据,所以这里的数据采用写时拷贝的方法进行存储
既然共享了代码,所以fork之后,父子进程会执行fork以下的代码,如果要让他们干不同的话,就要用if进行分流
那fork之前的代码呢?fork之前的代码子进程也是可以看到的
在一个函数中,return是一个语句吗?当然是!所以父子进程都会return一次,也就给了fork两个返回值(这里先这样理解,真实情况是os通过寄存器做到的返回两次)
因为父与子是1:n的关系,所以要给父进程返回子进程的id以便知道孩子有哪些,子进程没有孩子所以返回0
那么谁先运行呢?这个由os,pcb中的调度信息(优先级,时间片)等等决定
父子进程是相互独立的,父进程挂掉不会影响子进程,子进程挂了不会影响父进程
关于最后一个问题要学了后面的内容才能更好的理解
进程状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态
怎么知道呢?通过访问pcb里的一个变量数据——所以进程的状态修改本质上就是修改pcb里的存储进程状态的变量数据,并将其pcb列入不同的队列中
运行状态
如字面意思,就是各种资源已经准备好了,在运行队列当中了,随时可以被调度了
只要在运行队列中的进程,都是运行状态
每个cpu中都一定会设置一个运行队列
阻塞状态
我们写的代码,或多或少都要访问一些资源(键盘,磁盘,网卡等),但是当资源没有就位的时候,代码就无法继续往下运行,这个时候我们的进程就卡住了,
挂起状态(阻塞挂起)
当位于阻塞状态的进程越来越多时且os内存资源严重不足的时候,我们的电脑就会很卡,为了不让os挂掉,我们就要清理掉一些内存空间
所以当os内存资源不足的时候,处于阻塞状态的进程的代码段和数据会被交互到磁盘中的swap区域,以清理内存空间
当进程被os调度的时候,被置换出去的代码和数据又要重新被加载回内存中
linux进程的具体状态
R (running)
S (sleeping)
D (disk sleep)
T (stopped)
t (tracing stop)
X (dead)
Z (zombie)
R——其实就是运行状态,但是当代码中有大量的io操作的时候,即使代码正在运行也很难查出R状态,因为io太慢了,一直在等资源就位
状态后面的+表示前台进程——即该进程运行的时候bash的命令行无法运行,并且可以ctrl c终止
如果想要进程以后台进程的方式运行,要在后面加个&
后台进程可以输入命令行,但是不能ctrl c,如果想要终止后台进程,就要用到 killed -9 PID来终止进程
S——浅度睡眠
为阻塞状态的一种,会对外部的信号作出响应,可以被killed终止
进程在访问资源的时候,可能资源还没就位,就会被设为S状态
D——深度睡眠
为阻塞状态的一种,磁盘休眠状态,不会对外部的信号作出响应,不可以被killed终止,os也没资格
故事:假如os内的一个进程正在向磁盘写入客户的数据,但是os内已经没多少内存了,假如写入数据失败了,但是这个进程又被killed掉了,客户的信息丢失了,那么谁来负责呢?所以设置为D状态,不能被终止,除非拔掉电源
T——暂停状态(stopped)
当进程要访问一个资源的时候,可能暂时不让进程访问,就将其设为T状态——killed -SIGSTOP PID,继续运行killed -SIGCONT,暂停后自动会被换成后台进程
t——debug的时候追踪断点,在断点处让进程暂停
僵尸进程
我们为什么要创建一个进程?让一个进程来帮我们完成某项工作
我们要不要知道一个进程完成的怎么样?当然要!
所以进程在退出的时候,要有一些退出信息,表明自己把任务完成的怎么样(main函数通常都是return 0,return 1 /2/3会怎么样)
进程在退出的时候,退出信息会由os写到退出进程的pcb当中,可以允许代码和数据被释放,但是不允许pcb立即被释放,因为要让父进程或者os获取pcb里的退出信息,要是父进程或者os一直不获取呢,那就是所谓的僵尸状态(Z)了
如果一个进程Z状态了,父进程一直不回收它,他就要一直存在?是的,所以会造成内存泄漏(pcb在内存当中)
孤儿进程
当子进程的父进程直接退出了,子进程的pcb里的退出信息由谁来获取呢?这个时候就要由os接管子进程了,这里子进程被称为孤儿进程
关于进程的大概介绍也就这么多了,希望对大家能有所帮助,我也会继续努力输出Linux的知识