本文主要是学习Linux高性能服务器开发需要提前了解的知识,后续还会涉及到虚拟内存方面的内容,各位看官可以多了解了解,看到文章内有将的不清楚或者讲错的地方请各位一定留言,我看到后会第一时间验证并修正的。
Linux下的进程究竟是什么样的?
前言
本文主要介绍进程相关知识,内容会涉及到操作系统、计算机组成原理和部分Linux知识。本文是我在系统学习Linux服务器开发锁整理的前期知识准备。。。。
提示:以下是本篇文章正文内容,下面案例可供参考
一、进程是什么?
关于进程,我在百度百科和其他地方检索到几个词条,我在下面列出来,让我们大家对进程有个大概的概念。
- 百度百科:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
- CSDN技术博客:进程就是一段程序的运行状态,是动态进行的,是代码段和数据段经由CPU处理的整个动态过程。
我们随便打开一个技术博客或者是操作系统的书籍,关于进程都有详细的描述,虽然表达方式可能有所不同,但含义却是相差不多的。就比如说上面的两点,一个说的非常官方,一个说的有些随意,但我们还是可以从上面的内容去总结进程的部分特点。首先从百度百科我们可以知道,进程是离不开数据集合的,因为程序、也就是代码段运行需要操作数据集合。然后就是对操系统来说,进程是资源分配和调度的基本单位。那什么是资源分配和调度呢,我们慢慢来,最先的还是要搞清楚进程是什么。最后就是百度百科中提到的另一个重点内容——进程是线程的容器。这点涉及的东西有点多,后面会随着本章内容慢慢介绍的。
我们再从某位不知名的技术博主来说吧,他提到进程是一段程序的运行状态,那程序的运行状态到底是什么呢?不管我们去看出还是查资料,关于程序的运行状态都有统一的说法, 那就是从分配系统资源开始到程序执行结束归还资源的整个过程。然后我们再加两条限制,第一条就是这个过程不能中断,第二条就是程序因为访问临界资源造成阻塞不能算作程序中断。加了两条限制后,我们就可以总结进程的具体含义了,进程就是程序的动态体现,它是不能分割的,只有开始和结束两个状态,进程一旦开始就必须结束,满足要求的才能叫做进程。
二、进程的特点
1.进程的特点
进程的特点没啥好说的,王八的屁股,规定,记着就行…
- 并发性:可以和其他进程宏观表现为共同执行。
- 动态性:进程是执行中的程序。此外进程的动态性还体现在如下两个方面:首先,进程是动态产生、动态消亡的;其次,在进程的生存期内,其状态处于经常性的动态变化之中。
- 独立性:进程是相互独立的,进程间不会互相干扰,拥有自己的内存地址。
- 交往性:进程运行可能会影响到其他进程或者是作为其他进程的数据输入。
- 异步性:每个进程以相互独立,不可预知的速度进行着。
- 结构性:每个进程都用有一个进程控制块。
接下来,我们从操作系统和用户角度来分析以上提到的进程特点。
(1)并发性
首先是并发性,我们都知道,电脑只有一个CPU,虽然现在都有双核、四核,但这里不考虑,还是以传统早期的CPU为例(不得不说,双核和四核的出现确实给计算机带来了很大的变化)。CPU一个只能执行一个程序,也就是说,当CPU执行程序A时就没办法执行程序B,那上面提到的并发性是什么鬼呢?别急,我们慢慢来看。计算机体系中,CPU是由寄存器、运算器、控制器和定时器组成,当然还有Cache(高速缓冲区,用来解决主存和计算机之间读取速度不匹配问题),其中运算器负责运算从内存并放到寄存器中的数据,我们都知道,寄存器的读取速度是远超内存读取速度的,及时使用Cache也还是比不上寄存器的读取速度。那也就是说,程序的执行过程中,从内存读取数据当到寄存器中会浪费大量时间,而运算器进行数据处理是非常快的,可以达到纳秒级别。还记得我们之前提过,进程需要系统资源的,这分配的资源是包括CPU的,那么出现一种情况,也就是CPU是有很长一段时间是出于空闲时候的,这就造成了很大的资源浪费。那我们能不能先不分配CPU,等到程序的数据读入寄存器后才分配CPU呢,答案是可以的,这也就是进程并发性体现。当进程A在进行数据读取时让CPU先去执行其他进程的运算,再需要的时候按照一定策略去排队等候CPU,等分配到CPU资源时,因为数据已经提前写入寄存器了,可以直接运行,这样就很大程度上减少了系统资源的浪费。
(2)独立性
在开始之前,我先问个问题,进程所用的内存空间是虚拟内存还是物理内存?
上面的问题不着急回到,脑子里有这个疑问就行,我们接着往下看。独立性是指进程间拥有自己的内存空间,进程间的执行不会干扰到其他进程。可是,这在计算机上是如何体现的呢?首先,大家在使用计算机的时候有没有这种疑问,那就是电脑为什么可以同时运行那么软件,听音乐的同时还不耽误上午浏览。这是因为每个软件(对应一个进程)运行都是需要内存空间,如果同时运行的软件所需要的内存空间不会超过用户空间的话,是可以同时运行的。
(3)异步性
既然内存空间中存在多个进程,那么进程之间的执行顺序应该是怎样的呢?这就需要进程调度了。上面我们讲到我们不要在一开始就将CPU分配给进程,而是当进程的数据读入寄存器,下一步需要运算器对数据进行处理时才分配CPU资源。那么就会出现这种情况,当内存中存在多个进程时,不同进程的数据来源是不同的,也就是说进程A的输入可能是来源外设,进程B的数据可能来源于磁盘,而进程C的数据来源进程D的运算结果,或者换句话来说,每个进程数据准备所需的时间是不同的。那么这时候操作系统会根据不同进程数据准备时间的不同,按照某种策略将已经获取系统资源(除CPU资源外)放进就绪队列链表中,然后从就绪队列中读取进程分配进程资源。
三、进程调度
1.进程的几种状态
1.就绪态:进程已经获取到除CPU以外的所有系统资源。
2.运行态:就绪态的进程获取到CPU资源并处于运行中的状态。
3.阻塞态:进程因为访问临界资源或等待外设的数据输入的状态。
实际上关于进程的划分还有创建态和完成态,创建态是指“进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。”;完成态(结束态)是指“进程结束或出现错误或者因为接受到OS的信号导致中断,进入终止状态。” 虽然创建态和就绪态很相似,我们可以将两种状态合二为一,但是我们还是要清楚进程的创建过程,这时非常重要的。
2.进程调度
进程的调度就是操作系统按照某种策略去执行不同的进程,比如最短作业优先、最长作业优先、先来先服务、时间片轮转算法等,本质上就是把进程按照不同的策略放入就绪队列。关于算法就不多讲,后面可能会单独出一章讲。
3.进程间状态切换
我们以时间片轮转算法为例。首先我们需要考虑什么情况下进程会通过就绪态进入运行态,答案很简单,就是当就绪队列中的进程通过OS调度分配到CPU资源时。然后运行态的进程会面临这几种情况,第一种是在这个时间片能完成运算任务,这时进程会进入下一个状态(这个状态不一定是终止态)。第二种情况,时间片结束时运算任务还不能结束,这时候进程会重新回到就绪队列中排队。还有一种就是系统因为某种原因需要使用CPU(系统级别最高吗),进程也会重新回到就绪队列排队。当然还有一种就是因为进程需要访问临界资源或者等待某件事情发生(外设的数据输入),这时的进程会进入阻塞状态,阻塞状态的进程会在因为得到系统资源后某件事件完成重新进入就绪队列排队。
四、Linux下Fork子进程
1、函数介绍
终于到了要码代码的时候了,在开始之前我想需要简单介绍一下几个函数。
- getpid() :这个函数是Linux下获取进程ID的函数
- getppid():这个函数是Linux下获取父进程ID的函数
- fork():这个函数不用多说了,我要讲的就是它
- wait():调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到了一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。
- waitpid():功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞,由参数指定阻塞还是不阻塞.)。事实上wait()函数只是waitpid()函数的特例,内部实现wait()函数时,直接调用waitpid()。
2、wait和waitpid
这两个函数需要特别介绍,作用就是回收资源,我们要了解这两个函数具体工作流程,首先我们来看看帮助文档关于这两个函数是怎么介绍的。
- 引用头文件
#include <sys/types.h>
#include <sys/wait.h>
-
wait函数
wait 函数拥有一个 int* 类型的传出参数,它记录的是进程终止的状态信息。当进程调用wait时,wait会阻塞,直到检测到子进程终止或接受到信号后终止(死亡)之后,wait会回收该子进程所占用的系统资源并返回,如果穿的参数为NULL表示我们不关心子进程的死亡原因 -
waitpid函数
waitpid函数是wait函数的加强版,主要是因为waitpid函数多了两个可以操作的参数。
pid_t waitpid(pid_t pid, int* wstatus, int options);
wstatus:是记录子进程的死亡原因,如果我们不关心的话传NULL就可以,执行waitpid(-1, NULL, 0)的结果和wait(NULL)效果一样。
pid: 它是代表子进程的id,不同的传值代表不同的结果,pid > 0表示仅等待回收子进程的ID等于pid的进程资源,pid = -1 表示等待回收该进程下的任意一个子进程的系统资源;pid = 0等待同一个进程组的任意子进程结束,并回收系统资源,因为pid = 0代表的是父进程,但如果子进程进入别的进程组了,waitpid函数不理会;pid < -1代表等待一个指定进程组的任意子进程,进程组ID就是pid的绝对值。
options:目前Linux仅提供了两个选项,WNOHANG 和 WUNTRACED,WNOHANG代表即使没有子进程退出,waitpid也会返回,不过一般我们都传0即可,WUNTRACED的用法可以查看相关文档。
- 返回值
当正常返回时,会返回子进程的ID。
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD; - wait和waitpid的比较
wait只能处理一个进程的SIGCHLD,如果多个子并发子进程同时关闭,则会出现僵尸进程,使用waitpid则会避免这种情况。
3、僵尸进程和孤儿进程
1.僵尸进程
僵尸进程就是指进程的某些子进程运行结束,或者是因为接收到系统信号终止等原因致使进程结束,而未回收系统资源的进程,这类进程会一直占用系统的资源,属于那种占着茅坑不拉屎还浪费空气的那种进程。没啥用,多的话还会导致系统资源不够用。
2.孤儿进程
孤儿进程是指父进程不正常或者被信号杀死后,子进程未退出而被托孤给init进程(Linux的1号进程)
守护进程就是在后台运行,不与任何终端关联的进程,通常情况下守护进程在系统启动时就在运行,它们以root用户或者其他特殊用户(apache和postfix)运行,并能处理一些系统级的任务.习惯上守护进程的名字通常以d结尾(sshd),但这些不是必须的.
下面介绍一下创建守护进程的步骤:
· 调用fork(),创建新进程,它会是将来的守护进程.
· 在父进程中调用exit,保证子进程不是进程组长
· 调用setsid()创建新的会话区
· 将当前目录改成跟目录(如果把当前目录作为守护进程的目录,当前 目录不能被卸载他作为守护进程的工作目录)
· 将标准输入,标注输出,标准错误重定向到/dev/null
实例:
4、Fork函数使用和兄弟进程
5、进程间通信(简单介绍)
6、守护进程
总结
以上就是本文的所有内容,主要涉及进程的状态转换和Linux下有关进程的函数使用,进程通信介绍了一小部分,后面可能会单写一篇关于进程通信的文章,会介绍七种通信,着重介绍socket通信。