本文主要是介绍Linux进程初识:OS基础、fork函数创建进程、进程排队和进程状态讲解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
1、冯诺伊曼体系结构
问题一:为什么在体系结构中存在存储器(内存)?
存储单元总结:
问题二:为什么程序在运行的时候,必须把程序先加载到内存?
问题三:请解释,从你登录上qq开始和某位朋友聊天开始,数据的流动过程。
2、操作系统
2.1操作系统的概念:
我们首先要明白什么是管理:
2.2为什么要有操作系统?
2.3操作系统如何保证稳定和安全呢?(利用系统调用函数解决)
库函数和系统调用的关系:
3、那到底什么是进程呢?(重要!)
3.1、什么程序加载到内存,变成一个进程之后,我们要给每一个进程形成一个PCB对象呢?
3.2、PCB进程标识符:
4、fork函数初识
4.1、fork函数的作用:
4.2、使用方法(一般都用if分流)
4.3、关于fork函数的灵魂三问:
1、为什么给父进程返回子进程的pid,给子进程返回0?
2、fork函数为什么会返回两次?
3、这里的id为什么会同时满足 == 0 又满足>0呢?
问题:为什么我们在磁盘上将一个程序删了,这个程序还能运行呢?
5、进程排队:
6、进程状态:
6、1那什么是挂起状态呢?
6.2、Linux下具体的进程状态:
D状态深度睡眠又是什么呢?
6.3、为什么要有Z状态?
6.4、什么是僵尸Z状态?
6.5、僵尸进程危害:
7、什么是孤儿进程?
1、冯诺伊曼体系结构
截至目前,我们所认识的计算机,都是由一个个的硬件组件组成
- 输入单元:包括键盘, 鼠标,扫描仪, 写板等
- 中央处理器(CPU):含有运算器和控制器等
- 输出单元:显示器,打印机等
- 存储器:指的是内存(掉电易失)
- 运算器主要进行算术运算(加减乘除)和逻辑运算(判断真假)
设备是互相连接的,目的是让设备之间数据流动,本质是让设备之间进行数据的来回拷贝(拷贝的整体速度,是决定计算机效率的重要指标)
问题一:为什么在体系结构中存在存储器(内存)?
输入输出设备的运行速度就会拖累cpu的运行速度,跟木桶原理一个道理,那存储器并没有解决短板的问题,那怎么提高运行速度呢?
我们把内存看做一个非常大的缓存,介于设备和CPU之间
利用预存和缓存的机制,将计算机的效率最终变成了内存效率为主
存储单元总结:
距离CPU越近的存储单元,效率越高,造价贵,单体容量越小
距离CPU越远的存储单元,效率越低,造价便宜,单体容量大
所以内存的引入,可以让我们的计算机效率不错,且价格比较便宜,这样我们才能买的起电脑
问题二:为什么程序在运行的时候,必须把程序先加载到内存?
因为冯诺依曼体系决定的!
在数据层面,CPU只和内存打交道;外设只和内存打交道。
关于冯诺依曼,必须强调几点:
这里的存储器指的是内存
不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
一句话,所有设备都只能直接和内存打交道。
对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上,
问题三:请解释,从你登录上qq开始和某位朋友聊天开始,数据的流动过程。
我们先通过键盘输入消息,然后加载到内存,CPU从内存中读取数据,进行加密和计算后再放到内存,显示器(显示消息到屏幕上)和网卡(发送数据到网络)再从内存中读取数据。(忽略网络部分处理细节)
朋友电脑的网卡从网络上获取到了我们发送的数据,然后加载到内存,CPU从内存中读取数据,进行解密和计算后再放到内存,显示器再从内存读取相关的数据,显示到屏幕上。
总的来说,就是外设->内存->CPU->内存->外设
2、操作系统
2.1操作系统的概念:
操作系统是一个进行软硬件资源管理的软件。
我们首先要明白什么是管理:
管理就是做决策+做执行。
管理者和被管理者,并不需要见面,管理的本质是对人的信息(数据)做管理。
管理者的核心工作是做决策,根据数据做决策。
总结为六字真言:
先描述,再组织
语言的本质就是对数据的管理
2.2为什么要有操作系统?
对下管理好软硬件资源(手段),对上提供一个良好(稳定、高效、安全)的运行环境(目的)
2.3操作系统如何保证稳定和安全呢?(利用系统调用函数解决)
任何人不得访问操作系统中的任何数据,需要利用系统调用的函数
不能直接越过操作系统!系统调用接口很重要
所谓的系统调用接口,其实就是函数,用C语言设计的函数,由操作系统提供:系统调用函数
库函数和系统调用的关系:
只要库函数调用了系统调用,他们两个就必然是上下层的关系,库函数在上,系统调用在下,
3、那到底什么是进程呢?(重要!)
进程 = 内核PCB对象(内核的数据结构) + 可执行程序
这里的PCB到底是何方神圣呢?进程控制块(process control block)
在Linux环境下,PCB就是task_struct,存储进程的所有属性,操作系统内部的数据
未来,所有对进程的控制和操作,都只和进程的PCB有关,和进程的可执行程序没有关系
我们所说的让一个进程去排队,本质上是让PCB去排队,而不是让可执行程序去排队
对进程的管理,转换为对PCB对象的管理
3.1、什么程序加载到内存,变成一个进程之后,我们要给每一个进程形成一个PCB对象呢?
因为操作系统要进行管理!(先描述,再组织)
PCB这些内容都是在操作系统内部的数据
task_ struct内容分类
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
3.2、PCB进程标识符:
下面主要讲解标识符,描述进程的唯一标识符,用来区别其他进程
getpid是我们第一个学习的系统调用函数,用来获得该进程的标识符。
每一次启动进程的pid几乎都会变化,因为我的进程是一个新的进程!
一般在Linux中,普通进程,都有他的父进程!
通过系统调用获取进程标示符
进程id(PID)
父进程id(PPID)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
4、fork函数初识
4.1、fork函数的作用:
通过系统调用创建进程。它的作用是从已经存在的进程中创建一个子进程,而原进程称为父进程
4.2、使用方法(一般都用if分流)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0){ //child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else{ //father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
4.3、关于fork函数的灵魂三问:
1、为什么给父进程返回子进程的pid,给子进程返回0?
- 在父进程中,fork函数返回新创建子进程的进程ID。这是因为父进程需要通过这个返回值来跟踪和管理其创建的子进程。
- 在子进程中,fork函数返回0。这是因为子进程可以通过这个返回值来判断它是否成功创建,并且由于子进程只有一个父进程,它的ID可以通过getppid()获得父进程的ID,而子进程的ID(虽然与父进程ID不同)在fork函数返回时已经被系统内部记录下来。
父进程会有多个子进程,但是子进程只会有一个父进程,一对多的关系
2、fork函数为什么会返回两次?
当fork函数被调用时,它会创建一个新的子进程,这个子进程是父进程的一个复制品,它们共享相同的代码段和部分数据段。由于子进程是父进程的一个副本,因此它们都会执行fork函数之后的代码。这就导致了fork函数在父进程和子进程中都会“返回”,但返回的值不同
fork之后,我们的父和子都会进行,代码共享,一般而言,我们想让父子做不同的事情。调用fork函数,会返回不同的值分别给父子进程
父子进程执行不同的代码块
3、这里的id为什么会同时满足 == 0 又满足>0呢?
这是因为有两个进程,进程之间是具有独立性的,互相不能影响!
问题:为什么我们在磁盘上将一个程序删了,这个程序还能运行呢?
我们在运行一个程序时,本质是把程序从磁盘拷贝到内存,换句话来说就是你把这个程序在磁盘上删了,但是这个程序比较小,已经拷贝到内存,在内存上运行了,成为一个进程,就与磁盘上的程序没有关系了
5、进程排队:
进程为什么会排队呢?一定是在等待某种“资源”,比如下面的scanf就需要等待键盘给他传输资源。
只要是排队,一定是进程的task_struct进行排队。
6、进程状态:
教材上关于进程状态的表述:运行、阻塞、挂起
所谓的状态,本质就是一个整形变量,在task_struct中的一个整型变量
状态决定了什么?决定了你的后续动作,Linux中可能会存在多个进程都要根据它的状态执行后续动作(进程开始排队了!)
当我们的进程在进行等待软硬件资源的时候,资源如果没有就绪,我们的进程task_struct只能1、将自己设置为阻塞状态2、将自己的PCB连入等待的资源提供的等待队列。
状态的变迁,引起的是PCB会被OS变迁到不同的队列当中。
当我们的软硬件资源准备就绪后,进程状态就会从阻塞状态调整到运行状态!
6、1那什么是挂起状态呢?
我们这里主要讲的是阻塞挂起,这个状态的前提是计算机资源比较吃紧了!
挂起状态就是将数据从内存,换出到磁盘上面 ,当计算机资源恢复后,数据会从外设转入到内存中
6.2、Linux下具体的进程状态:
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态下面代码是状态在kernel源代码里定义:
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 */
};
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的程序可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
S状态其实与操作系统中的阻塞状态(等待某种资源)是一致的!!!
D状态深度睡眠又是什么呢?和S睡眠有什么区别?
深度睡眠,不可中断睡眠。
相当于给进程一个免死金牌,我们要知道在Linux环境下,在操作系统逼急的时候,是会杀掉进程!如果这个进程很重要,就会造成很大的损失,因此这个D状态就相当于给了进程一个免死金牌,不会被进程所杀掉。
注意D状态依旧是阻塞状态
kill -19 pid 就会让一个进程进入T状态,也就是暂停状态
继续运行就是 kill -18 pid
子进程最终的状态都是Z
注意状态标识后面有+,就代表是前台进程,可以直接^C杀死,但是如果没有+,就说明这个进程变成了后台进程,不能用^C杀死,只能kill-9方法
后台进程,我们用普通的^C是停止不掉的,那怎么停止呢?
我们可以使用这样的命令:kill -9 pid(该进程的pid)
6.3、为什么要有Z状态?
创建进程是希望这个进程给用户完成工作的,子进程必须得有结果数据返回给父进程
6.4、什么是僵尸Z状态?
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)
没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
6.5、僵尸进程危害:
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎
么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话
说,Z状态一直不退出,PCB一直都要维护?是的!
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?也就会造成内存的泄露!
7、什么是孤儿进程?
父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”
这篇关于Linux进程初识:OS基础、fork函数创建进程、进程排队和进程状态讲解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!