文章目录

  • 1.冯诺依曼体系结构
    • 1.1计算机体系的奠基人
    • 1.2冯诺伊曼体系讲解
    • 1.3机器间的数据交互
  • 2.操作系统(OS)
    • 2.1操作系统基本概念
    • 2.2操作系统管理理解
    • 2.3操作系统进程管理
    • 2.4Centos系统下进程操作
    • 2.5进程控制块(PCB)详解
      • 2.5.1pid ppid(标识符)
      • 2.5.2STAT(状态)
      • 2.5.3优先级
      • 2.5.4.时间片
      • 2.5.5程序计数器(PC/IP)
      • 2.5.6上下文数据
      • 2.5.7I/O状态信息
      • 2.5.8记账信息
  • 3.进程详解
    • 3.1系统调用创建进程
    • 3.2fork函数(初阶)
    • 3.3进程状态详解
    • 3.4孤儿进程详解
    • 3.5进程优先级详解
    • 3.6进程调度队列
    • 3.7其他概念
  • 4.环境变量
    • 4.1环境变量基本概念
    • 4.2查看环境变量
    • 4.3修改环境变量
    • 4.4带参的main函数
    • 4.5环境变量具有全局性
    • 4.6获取/修改/删除环境变量的方式
  • 5.进程地址空间
    • 5.1了解进程地址空间
    • 5.2进程地址空间的作用
  • [结语]:

1.冯诺依曼体系结构

1.1计算机体系的奠基人

冯诺依曼:数学家,早期提出计算机的基本硬件架构,计算机奠基人之一
阿兰图灵:计算机建模,计算机基本软件架构,也是著名的计算机奠基人

1.2冯诺伊曼体系讲解

我们常见的计算机,如笔记本。我们不常见的计算,如服务器,大部分都遵守冯诺依曼体系结构


因为原计算机结构存在缺陷,冯诺伊曼对原有体系进行改进

结论:
1.站在硬件角度&&数据层面上,CPU只和内存打交道,外设也只和内存打交道
2.输入设备:键盘,网卡,硬盘,话筒,摄像头
3.输出设备:显示器,音响,网卡,硬盘
4.数据要处理,必须预装载到内存中,局部性原理,操作系统完成
5.寄存器不仅仅CPU中具有,其他外设也是有的
6.各种硬件单元之间链接用的是:总线(IO总线、系统总线)
7.其实CPU和输入输出设备并非完全没有联系,当输入设备数据录入完毕后,会给CPU发送一个电脉冲,提示CPU运行并开启存储器等进行工作。类似的网卡之间也有这样的功能,不然的话网卡中即使有数据CPU不知到的话数据也是无法读取的。

程序要运行之前,为什么必须要加载到内存中?
答:可执行程序(文件)是在硬盘上(外设),外设上的数据想要和CPU产生交互必须先被加载到内存中

1.3机器间的数据交互

1.两台机器传输信息硬件传输路径简略示意图(忽略网络部分)

2.两台机器传输文件硬件传输路径简略示意图

与信息传输相似,只是外设有些许变化
结论:
1.网卡/硬盘 既能从内存中读取数据,也可以向内存中输出数据
2.软件必须按照硬件来设计,软件实现的功能必须要在硬件理论可以实现的范畴之内

2.操作系统(OS)

2.1操作系统基本概念

操作系统是一款软件:计算机通过操作系统来进行硬件,内存,文件,驱动,进程管理
以下是计算机各个层级之间的关系的简略示意图

我们由下向上分析
1.为什么存在驱动层:以键盘(硬件)输入设备为例,为什么操作系统不能直接从键盘上读取数据呢。因为市面上有很多的键盘,它们的输入方法有所不同。若操作系统直接从键盘中读取数据,那么根据键盘的类型,我们必须直接对操作系统进行源码的修改,将键盘和我们的操作系统进行匹配。这显然成本非常高,所以我们在中间加了一个驱动层。键盘厂商直接进行驱动的编写,将我们的硬件和操作系统解耦(说人话就是匹配起来)。这样就可以很好的让我们的硬件来适应操作系统,便于操作系统更好的管理
2.为什么存在系统调用接口:因为用户想要通过操作系统对计算机进行管理,但是操作系统也是一款软件,Linux系统是由C语言编写而成,那么用户的操作就有可能对操作系统产生影响,所以为了保护我们的操作系统,对操作系统进行封装,提供接口帮助我们管理使用操作系统。
3.为什么存在库:因为系统调用接口还是较为复杂需要使用者对操作系统有较为深刻的理解,对于普通的用户来说学习成本过高。我们将系统调用接口再进一步封装打包形成各种各样的库,便于普通人学习使用。我们再通过库来编写我们的图形化界面,命令行或者说是开发自己的用户级别的软件等(使用库开发出的软件是用户级的)

为了加深理解,再放一张各个区域所对应的硬软件的示意图

2.2操作系统管理理解


管理者:对数据结构的管理,先描述,再组织
执行者:做具体的事情并且收集数据

在大学校园中,校长并不直接和学生打交道,但是校长却可以管理着大量学生。学生报考进入学校,辅导员收集学生信息给校长,校长将很多辅导员收集到的学生信息进行收集,构建文档进行存贮,并将学生的基本信息,成绩信息,健康信息等进行分类。当学校要参加全国比赛时**,校长通过调取整个学校的学生信息,选出**对应学科成绩最优秀的十名交给执行者也就是辅导员,辅导员找到学生并配备老师给学生补习。这就是一个典型的管理体系。校长(管理者)依据学生信息(数据)进行决策,辅导员(驱动)进行学生,普通教师(被管理者)的组织。

而在我们的计算机体系中,操作系统就是这个校长,各种硬件、软件设备就是所谓的学生,驱动就是收集,传递,执行决策的辅导员。各种硬件软件都包含很多的数据信息,我们的操作系统对硬件软件的属性进行描述,Linux系统构建结构体(struct)来对硬件软件的属性进行抽象描述。就像校长将学生进行信息化,变成文档中的各种数据。这样管理者就可以认识硬软件,并对其进行管理。而如何将硬软件抽象出的结构体组织起来,这就要使用顺序表,链表和其他高效的数据结构,便于操作系统增删查改硬件软件的数据。

总结:
struct结构体 :描述
数据结构 :组织

2.3操作系统进程管理

进程:运行起来的程序,即加载到内存中的程序就成为了进程
查看进程指令:ps aux

1.可执行程序准备运行时,会被加载到内存中。在内存中的程序就成为了一个进程
2.操作系统对每一个进程都会生成一个进程控制块(PCB),PCB是对进程的描述,抽象了进程的各种属性信息
3.操作系统进行进程管理是通过管理一个个PCB,并非直接和进程交互。我们可以通过数据结构(如链表,顺序表等数据结构)将PCB链接起来,对数据结构进行增删查改也就完成了对进程的运行,中止,进程信息打印,修改进程属性的操作

结论:
1.进程 > 可执行程序 【+内核数据结构】
2.进程->PCB (描述)
3.PCB在操作系统内生成数据结构(组织)

在LInux系统下进程管理简略示意图

在Linux系统中,每个PCB都是一个结点,所有PCB一起构成了一个双向带头链表,操作系统中存储了头节点的地址,所以操作系统就可以通过头节点来访问整个链表的所有数据。并对链表进行管理

综上所述
创建进程:可执行程序加载到内存中,操作系统创建PCB并链接到双链表当中
退出进程:将此进程PCB从双链表中删除,同时清除进程在内存中的数据

2.4Centos系统下进程操作

获取进程的相关信息我们可以通过查看/proc/1文件夹

--首先我们先编写两个文件
[clx@VM-20-6-centos lesson_7_15]$ ll
total 8
-rw-rw-r-- 1 clx clx 103 Jul 16 19:50 Makefile
-rw-rw-r-- 1 clx clx 132 Jul 16 19:52 mytest.c[clx@VM-20-6-centos lesson_7_15]$ cat mytest.c
#include <stdio.h>
#include <unistd.h>int main()
{while (1){printf("I'm a process\n");sleep(1);}return 0;
}
[clx@VM-20-6-centos lesson_7_15]$ cat Makefile
mytest:mytest.ogcc -o $@ $^
mytest.o:mytest.cgcc -c $<.PHONY:clean
clean:rm -f *.o mytest  
运行我们的代码
[clx@VM-20-6-centos lesson_7_15]$ make
gcc -c mytest.c
gcc -o mytest mytest.o
[clx@VM-20-6-centos lesson_7_15]$ ./mytest
I'm a process
I'm a process
I'm a process

此处打开了两个云服务器,不然程序在前端运行时无法输入指令

如何打开两个云服务器


这样我们就可以得到同一个用户的云服务器界面了

使用ps aux指令查看进程信息  |  && 是执行完前面指令马上进行后面指令的意思
[clx@VM-20-6-centos lesson_7_15]$ ps aux | grep mytest
clx      11912  0.0  0.0   7332   380 pts/4    S+   20:32   0:00 ./mytest
clx      12100  0.0  0.0 115932  1020 pts/5    S+   20:33   0:00 grep --color=auto mytest

我们发现mytest有两个进程在运作,原因是我们筛选进程时用了grep指令**,grep指令也带有关键字mytest**,所以也被筛选了出来。为了获得更加详细的头部信息我们可以带上head -1 && ps aux,为了过滤掉grep进程我们可以使用grep -v grep选项

[clx@VM-20-6-centos lesson_7_15]$ ps aux | head -1 && ps aux| grep mytest | grep -v grep
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
clx      11912  0.0  0.0   7332   380 pts/4    S+   20:32   0:00 ./mytest符号解释:
USER:用户
PID:进程对应的唯一标识符
%CPU:CPU的使用率
STAT:状态
终结进程 kill -9 + 进程对应的唯一标识符
发送9号信号给mytest
[clx@VM-20-6-centos lesson_7_15]$ kill -9 11912
[clx@VM-20-6-centos lesson_7_15]$ 

2.5进程控制块(PCB)详解

本质:C语言中的结构体,内部包含进程的各种属性信息
Linux PCB 名称:task_struct

PCB中有哪些属性信息:
1.pid ,ppid(标识符)
2.优先级
3.代码和数据在内存中的位置
4.时间片
5.上下文信息
6.

2.5.1pid ppid(标识符)

pid:进程对应的唯一标识符
ppid:进程的父进程对应的唯一标识符
我们可以使用getpid和getppid来获取pid和ppid的信息

头文件:#include <sys/types.h>
[clx@VM-20-6-centos lesson_7_15]$ man getpid

首先修改一下mytest.c代码
[clx@VM-20-6-centos lesson_7_15]$ cat mytest.c
#include <stdio.h>
#include <unistd.h>int main()
{while (1){printf("I'm a process.. pid:%d  ppid:%d\n", getpid(), getppid());sleep(1);}return 0;
}
[clx@VM-20-6-centos lesson_7_15]$ make
gcc -c mytest.c
gcc -o mytest mytest.o
[clx@VM-20-6-centos lesson_7_15]$ ./mytest  --运行程序
I'm a process.. pid:17828  ppid:10793
I'm a process.. pid:17828  ppid:10793
I'm a process.. pid:17828  ppid:10793
I'm a process.. pid:17828  ppid:10793
^C
[clx@VM-20-6-centos lesson_7_15]$ ./mytest
I'm a process.. pid:17846  ppid:10793
I'm a process.. pid:17846  ppid:10793
I'm a process.. pid:17846  ppid:10793
I'm a process.. pid:17846  ppid:10793我们发现相同程序,生成的进程pid是不一样的,但是它们的父进程的pid都是相同的
打开我们另外一个窗口
[clx@VM-20-6-centos lesson_7_15]$ ps ajx | head -1 && ps axj | grep 10793 | grep -v grepPPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
10792 10793 10793 10793 pts/4    19986 Ss    1001   0:00 -bash
10793 19986 19986 10793 pts/4    19986 S+    1001   0:00 ./mytest我们曾经说过登录操作系统会创建一个-bash进程,我们后来进行的指令也好,程序也好生成
的进程都是-bash的子进程,所以如果我们中止了-bash进程,我们的云服务器也就中止了[clx@VM-20-6-centos lesson_7_15]$ kill -9 10793
Connection closed by foreign host.
Channel closed from remote host(进击的小白) at 21:21:06.Type `help' to learn how to use Xshell prompt.
[C:\~]$ --bash被中止了
[clx@VM-20-6-centos lesson_7_15]$ ps ajx | head -1 && ps axj | grep 10793 | grep -v grepPPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

2.5.2STAT(状态)

进程的状态:S正在休眠,R正在运行,+前台进程

我们将mytest跑起来
[clx@VM-20-6-centos ~]$ ps ajx | head -1 && ps axj | grep mytest | grep -v grepPPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
22828 23031 23031 22828 pts/5    23031 S+    1001   0:00 ./mytestSTAT就是所谓的状态
S表示休眠 R表示正在运行

此时我们的进程确实是在进行的,为什么系统会显示其在休眠呢。
因为CPU的速率很快,我们的进程需要将内存中的数据放入缓冲区,显示器的速率相较于CPU来说非常慢,所以我们的进程有99%以上时间是在休眠的,它在等待数据在缓冲区向显示器上打印。而指令返回的是它查看的瞬间进程的状态

那么我们如何查看到一个正在运行状态的进程呢?其实很简单,只要取消打印就行了

修改后的mytest.c代码
[clx@VM-20-6-centos lesson_7_15]$ cat mytest.c
#include <stdio.h>
#include <unistd.h>int main()
{while (1){// printf("I'm a process.. pid:%d  ppid:%d\n", getpid(), getppid());// sleep(1);}return 0;
}
运行mytest
[clx@VM-20-6-centos lesson_7_15]$ make
gcc -c mytest.c
gcc -o mytest mytest.o
[clx@VM-20-6-centos lesson_7_15]$ ./mytest输出进程状态
[clx@VM-20-6-centos ~]$ ps ajx | head -1 && ps axj | grep mytest | grep -v grepPPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
24928 25216 25216 24928 pts/6    25216 R+    1001   0:20 ./mytest

Linux操作系统命令行中,只允许有一个前台进程,./mytest进程是一个前台进程,所以当./mytest在运行时,就不能使用其他命令行了。前台进程的状态后面有个+。我们可以在前台进程的后面加上一个&符号,这样它就变成了后台进程

后台进程可以有多个,而且不影响命令行的输入

后台进程
[clx@VM-20-6-centos lesson_7_15]$ ./mytest &
[1] 26806
[clx@VM-20-6-centos lesson_7_15]$ ps ajx | head -1 && ps axj | grep mytest | grep -v grepPPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
24928 26806 26806 24928 pts/6    26914 R     1001   0:14 ./mytest中止后台进程
kill -9 +后台进程pid
[clx@VM-20-6-centos lesson_7_15]$ kill -9 26806
[1]+  Killed                  ./mytest

2.5.3优先级

优先级:谁先谁后拿到某种资源
为什么存在优先级:资源有限(CPU),进程是无上限的
如何理解进程排队:本质上操作系统根据PCB上的优先级属性信息进行进程排队,优先级越高的离头节点越近

2.5.4.时间片

本质:记录了进程一次调用CPU的时间
在一台机器中,同一时间有很多进程再跑。但是我们的CPU只有一个,假如我们的一个程序陷入了死循环,那么CPU本应该一直执行死循环进程,这样我们就不能进行其他操作了。但是现实并不是这样,因为再PCB中存在时间片这样的概念,时间片中记录此进程一次可以调用CPU的时间当CPU对此进程处理了其时间片的时间后,就会切换进程,因为时间片很短,CPU就会在很多的进程之间来回切换使得多个进程在一段时间内都获得推进。造成了多个进程同时进行的现象

2.5.5程序计数器(PC/IP)

CPU中的指令寄存器(EIP)
CPU和内存之间的交互简化为:取指令,分析指令,执行指令
而指令寄存器储存的就是CPU正在执行的指令的下一条指令的地址
所以所谓的函数跳转,分支判断,循环等都是通过修改EIP来实现的

程序计数器(IP):I(指令)P(指针)
本质:进程中即将被执行的下一条指令的地址
CPU在运行时会根据时间片进行进程切换,但是进程运行会产生一些临时文件和数据。程序计数器就会生成一块空间来保存这些数据,并且记录所开空间的地址和进程运行的位置,当CPU再次切换到此的进程时,程序计数器就告诉CPU该从哪开始读取指令,并提供相关数据

2.5.6上下文数据

本质:CPU内部只有一套寄存器,计算机需要内存中的数据移动到CPU寄存器中,CPU进行执行指令又要将处理好的数据放在寄存器中然后交给内存。这些寄存器内的数据就是所谓的上下文数据

那当进程被切换时,(CPU)寄存器中的数据还没来得即处理或者还没来得及传给内存。我们必须要将寄存器中的数据保存起来,等再一次切换到此进程时才能保证进程的正常推进。所以我们将寄存器中的数据进行打包放在我们的PCB中。下一次CPU就可以通过PCB读取上下文数据

2.5.7I/O状态信息

本质:分配给进程的I/O设备和进程使用的文件列表
我们写的程序被加载到内存中运行产生进程,进程的运行需要我们磁盘中的文件数据代码等,PCB会储存进程运行的数据来源(文件列表),并且进程运行过程中也要读取或者输出数据,会用到I/O设备,PCB就将进程需要的I/O信息写入,便于之后调用

2.5.8记账信息

本质:进程被CPU处理时间总和,时间限制等
内存中有很多进程在同时运行,但是CPU是在各种进程中切换运作**,记账信息就表示此进程实际使用了CPU多长时间,**可能一个进程规定使用CPU一秒钟,一秒还处理不完就报错,这样记账信息就成了重要的参考

3.进程详解

3.1系统调用创建进程

系统创建进程函数:fork()

通过man 2 fork 查看fork()函数的基本信息
NAMEfork - create a child processSYNOPSIS#include <unistd.h>pid_t fork(void);头文件 #include <unistd.h>
返回值 pid_t(无符号整形)
参数  void

接下来我们逐步进行fork 函数讲解
1.创建myproc.c 和 Makefile

//myproc.c
#include <stdio.h>
#include <unistd.h>int main()
{fork()printf("I'm running ..\n");while(1){printf("I'm a process .. pid = %d, ppid = %d\n",getpid(), getppid());sleep(1);}return 0;
}
//Makefile
myproc:myproc.cgcc -o $@ $^
.PHONY:clean
clean:rm -f myproc

2.运行生成的可执行程序

I'm a process .. pid = 22370, ppid = 22369
I'm a process .. pid = 22369, ppid = 17891
I'm a process .. pid = 22370, ppid = 22369
I'm a process .. pid = 22369, ppid = 17891
I'm a process .. pid = 22370, ppid = 22369
I'm a process .. pid = 22369, ppid = 17891

实验现象:我们发现每一秒打印了两条信息,并且两条信息的pid是不一样的,fork创建出的子进程的ppid是父进程的pid
实验结论:fork创建出的进程是原进程的子进程,子进程可以使用父进程中的代码和数据,并和父进程一起运行
实验结果:系统中多了一个进程,操作系统为这个子进程创建task_struct

3.2fork函数(初阶)

1.fork函数的返回值
fork函数会有两个返回值,给父进程返回子进程的pid,给子进程返回零

实验:fork返回值

//修改myproc.c
#include <stdio.h>
#include <unistd.h>int main()
{printf("I'm running ..\n");pid_t id = fork();printf("id = %d, pid = %d, ppid = %d\n", id, getpid(), getppid());sleep(1);return 0;
}
[clx@VM-20-6-centos lesson_Linux]$ make clean; make
rm -f myproc
gcc -o myproc myproc.c
[clx@VM-20-6-centos lesson_Linux]$ ./myproc
I'm running ..
id = 27903, pid = 27902, ppid = 17891
id = 0, pid = 27903, ppid = 27902

我们发现一次printf输出了两次,上面那条(父)的id是下面那条(子)的pid,下面那条(子)的ppid是上面那条(父)的pid。
结论:
1.fork();语句之后增加了一个进程,且这个进程是原进程的子进程
2 .fork语句返回了两个值,给父返回子的pid,给子返回零

2.fork双返回值的作用

因为fork函数生成的返回值不同,我们可以结合返回值和条件判断语句,让父子进程进入两个不同的分支执行不同的代码。

#include <stdio.h>
#include <unistd.h>int main()
{printf("I'm running ..\n");pid_t id = fork();if (id == 0){while(1){printf("I'm child process .. pid = %d , ppid = %d\n", getpid(), getppid());sleep(1);}}else if(id > 0){while(1){printf("I'm father process .. pid = %d, ppid = %d\n", getpid(), getppid());sleep(2);}}return 0;
}
[clx@VM-20-6-centos lesson_Linux]$ ./myproc
I'm running ..
I'm father process .. pid = 11871, ppid = 8599
I'm child process .. pid = 11872 , ppid = 11871
I'm child process .. pid = 11872 , ppid = 11871
I'm father process .. pid = 11871, ppid = 8599
I'm child process .. pid = 11872 , ppid = 11871
I'm child process .. pid = 11872 , ppid = 11871
I'm father process .. pid = 11871, ppid = 8599
I'm child process .. pid = 11872 , ppid = 11871
I'm child process .. pid = 11872 , ppid = 11871
I'm father process .. pid = 11871, ppid = 8599
I'm child process .. pid = 11872 , ppid = 11871
I'm child process .. pid = 11872 , ppid = 11871

我们发现父进程每两秒打印一次语句,而子进程每一秒打印一次。通过fork语句父子进程成功实现功能分离

3.3进程状态详解

1.R运行状态:并不意味着进程一定在运行,它表明进程要么在运行中,要么在运行队列中。R状态的进程表示我已经准备好了,请随时来调度我

调度队列(FIFO):操作系统会将所有的R状态的PCB通过双链表链接起来,形成一个队列

2.S浅度睡眠状态:表示进程已经准备好了,随时可以被唤醒然后加入调度队列

3.D深度睡眠状态:进程在控制输入或者输出时,因为外设速度比较慢,所以进程会等待外设输入或者输出数据。此时就算时操作系统也不能杀死进程。若输入输出还没有完毕,一旦此进程被杀死,输入或者输出失败后拿着数据等带并不能得到重新输入的指令,可能会导致数据的丢失。

4.T暂停状态:可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。

kill -l指令:查看系统信号集
[clx@VM-20-6-centos lesson_Linux]$ kill -l1) SIGHUP     2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP6) SIGABRT    7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG  24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF 28) SIGWINCH    29) SIGIO   30) SIGPWR
31) SIGSYS  34) SIGRTMIN    35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX    

5.Z僵尸状态:进程已经退出,但在系统层面,曾经申请的资源,没有被立即释放,而是暂存一段时间,供OS(父进程)进行读取,这段时间进程的状态就是僵尸状态

作用:进程创建的目的是为了完成某种工作,当进程结束了,调用方有必要知道任务是否成功,进程就会将自己的退出码等信息暂存在PBC中,等待父进程或者操作系统读取

C语言中常用的main函数最后总是带着return 0;
这个零时返回给操作系统来判断我们的程序是否正常退出的
我们可以使用echo $?指令来获得最近结束的进程的退出码
接下来我们将改造我们的程序,创建一个僵尸状态的进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main()
{printf("I'm running ..\n");pid_t id = fork();if (id == 0){while(1){int count = 5;while (count){printf("I'm child pid = %d , ppid = %d  cout = %d ..\n", getpid(), getppid(), count);count--;sleep(1);}printf("I'm quit ...\n");exit(1);}}else if(id > 0){while(1){printf("I'm father process .. pid = %d, ppid = %d\n", getpid(), getppid());sleep(1);}}return 0;
}我们让子进程跑五秒就中止它,子进程结束后就会成为僵尸进程等待父进程读取它的数据,但我们的
父进程中并没有写关于子进程数据读取的相关代码,所以子进程会维持僵尸状态

这个时进程监视器的代码
while :; do ps aux | head -1 && ps aux | grep   myproc | grep -v grep; echo " ##";sleep 1;done

最后我们来讲一讲僵尸进程的危害,假如一个进程退出一直没有人来读取它的数据,那么它的PCB并不会被操作系统回收,那么这一块内存将会被这个PCB一直占用,那么就会造成内存泄漏。

6.X死亡状态:当僵尸状态完毕,数据和资源被操作系统和父进程读取完毕后,进程就死亡了。这个状态仅仅是个返回状态,我们无法在任务列表中看到这个状态

3.4孤儿进程详解

操作系统所对应的进程是一号进程

孤儿进程:本质上是父进程和子进程一起运行,父进程退出,但是子进程还在运行形成孤儿进程 ,但孤儿进程会立马被我们的操作系统(1号进程)领养,当子进程退出变成僵尸,就由操作系统来对它的数据进行回收处理

//修改我们的myproc.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{printf("I'm running .. \n");pid_t id = fork();if (id == 0){int count = 5;while(count){printf("I'm child .. pid = %d , ppid = %d\n", getpid(), getppid());sleep(1);count--;}exit(1);}else if(id > 0){int count = 3;while(count){printf("I'm father .. coumt = %d, pid = %d, ppid = %d\n", count, getpid(), getppid());count--;sleep(1);}exit(2);}else{printf("I'm fail..\n");}return 0;
}
[clx@VM-20-6-centos lesson_Linux]$ make clean; make
rm -f myproc
gcc -o myproc myproc.c
[clx@VM-20-6-centos lesson_Linux]$ ./myproc
I'm running ..
I'm father .. coumt = 3, pid = 18956, ppid = 18175
I'm child .. pid = 18957 , ppid = 18956
I'm father .. coumt = 2, pid = 18956, ppid = 18175
I'm child .. pid = 18957 , ppid = 18956
I'm father .. coumt = 1, pid = 18956, ppid = 18175
I'm child .. pid = 18957 , ppid = 18956
[clx@VM-20-6-centos lesson_Linux]$ I'm child .. pid = 18957 , ppid = 1
I'm child .. pid = 18957 , ppid = 1
[clx@VM-20-6-centos lesson_Linux]$ I'm child .. pid = 19834 , ppid = 1
echo $?
2

我们发现,父进程跑了三秒后退出了,子进程变为孤儿进程,ppid就变成了1号进程,说明子进程确实是被操作系统给接管了。输入指令echo $?, 系统返回2 说明我们的父进程确实是被-bash进程回收的

3.5进程优先级详解

进程优先级:CPU资源分配的先后顺序。将优先权高的进程具有优先执行的权力。可以改善我们的系统性能

查看Linux系统下进程优先级:

1.写一个死循环
2.调用ps -al 指令(可以将shell产生的进程打出来)
[clx@VM-20-6-centos test2_Linux]$ ps -al
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1001 29200 18175  0  80   0 -  1833 hrtime pts/4    00:00:00 myproc
0 R  1001 29222 26164  0  80   0 - 38595 -      pts/5    00:00:00 ps

PRI:代表这个进程可被执行的优先级,值越小优先级越高
NI:进程的nice值,其表示 进程可被执行的优先级修正数据**(取值范围 : -20 - 19)**
系统进行判断的PRI(打印在屏幕上的)=PRI(系统默认设定的) + NI,所以PRI越低,优先级越高

top命令更改已存在的进程nice

1.运行我们的程序
2.ps -al 找到我们运行的进程的pid
3.输入top
4.输入r
5.输入想要修改的进程的pid
6.输入修改的nice值
注意:如果想要将我们进程的优先级调高需要使用sudo top提升权限

因为top指令输出的东西很长,不太好复制这里就不演示了

3.6进程调度队列

每一个CPU都有一个runqueue,这是一种数据结构

进程按照优先级放在queue[100]到queue[139]的位子上,若有相同的进程,它们会相互连接起来,CPU从小到大处理进程

活动队列:时间片还未结束所有进程按照优先级放在队列中
过期队列:时间片结束的进程会放在过期队列中,和活动队列结构相同

3.7其他概念

  • 竞争性:系统进程数量多,CPU资源少,所以进程之间具有竞争性属性。为高效完成任务,更合理的竞争资源于是有了优先级
  • 独立性:多个进程运行,需要独享某种资源,多进程运行期间互不干扰
  • 并行:多个进程在多个CPU下分别同时运行,称之为并行
  • 并发:多个进程在一个CPU下以进程切换的模式,在一段时间内多个进程都得以推进,称之为并发

4.环境变量

4.1环境变量基本概念

环境变量:一般是指在操作系统中来指定操作系统运行环境的一些参数,它可以帮助系统进行管理

Windows操作系统下的环境变量列表

如图,Windows系统下的环境变量有路径,有数字等等。例如Path变量就是一个路径

4.2查看环境变量

env指令:查看大量环境变量

1.查看home变量

[clx@VM-20-6-centos ~]$ echo $HOME
/home/clx

2.查看shell变量

[clx@VM-20-6-centos ~]$ echo $SHELL
/bin/bash

3.查看Path变量:

当然Linux操作系统也有自己的环境变量,也有Path变量。
我们可以通过echo $PATH 来读取Linux系统下的Path变量

[clx@VM-20-6-centos test4_C]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/clx/.local/bin:/home/clx/bin
我们发现Path变量是很多路径,并且各个路径用:相连

那么环境变量的作用是什么呢?接下来将用Path变量来举例说明

在平时写C/C++代码时,生成的可执行程序将其运行需要在文件名前面加上
‘ ./ ’ ,这个符号是为了告诉操作系统,我要运行的程序在当前目录下。但是我们发现平常使用ls ll pwd等指令时并没有附上它的绝对路径,但是操作系统却可以找到它们。**这是因为操作系统拥有环境变量,在我们使用命令行时,它会拿着我们的命令在设置的环境变量(Path)中的路径下查找并运行。**所以将自己写的可执行程序放入Path中写的路径中,或者将当前目录加入到Path中,这样也可以不加./运行程序

这里不建议大家将自己的程序放入Path环境变量内的路径中,因为时间久了你可能会忘记自己在此路径下的存的指令,污染我们的指令池。所以加入真的想要使用,最好直接将可执行程序所在的路径加入到我们的Path变量中,便于以后查找和删除

4.3修改环境变量

如果想要将当前路径导入到环境变量中改怎么做呢

[clx@VM-20-6-centos test4_C]$ ll
total 20
-rw-rw-r-- 1 clx clx   66 Jul 21 14:50 Makefile
-rwxrwxr-x 1 clx clx 8360 Jul 21 14:53 mytest     --当前路径下的可执行文件
-rw-rw-r-- 1 clx clx  221 Jul 21 14:53 mytest.c
[clx@VM-20-6-centos test4_C]$ echo $PATH --当前Path环境变量
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/clx/.local/bin:/home/clx/bin
[clx@VM-20-6-centos test4_C]$ pwd
/home/clx/Lesson_Linux/lesson_7_21/test4_C   --当前路径
[clx@VM-20-6-centos test4_C]$ export PATH=$PATH:/home/clx/Lesson_Linux/lesson_7_21/test4_C
[clx@VM-20-6-centos test4_C]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/clx/.local/bin:/home/clx/bin:/home/clx/Lesson_Linux/lesson_7_21/test4_C
[clx@VM-20-6-centos test4_C]$ mytest      --直接使用mytest
20                       --输出结果

添加路径的指令比较长,把它从里面挑出来了
export PATH=$PATH: + 绝对路径

[clx@VM-20-6-centos test4_C]$ export PATH=$PATH:/home/clx/Lesson_Linux/lesson_7_21/test4_C

我们还可以给我们的系统增加环境变量

export MYVAL=100 增加一个名为MYVAL值为100的环境变量如果我们没有加上MYVAL,那么也会生成一个变量,但这种变量叫本地变量
我们可以使用指令set查询
set可以查询终端中的所有变量,包括环境变量unset MYVAL 就可以取消环境变量

4.4带参的main函数

int main(int argc, char *argv[], char *envp[])
argc 是 argv指针数组所含元素的个数
argv是一个指针数组,最后一个元素为NULL
envp也是一个指针数组,寻找环境变量,最后一个元素为NULL

假如我们输入指令./mytest -a -b -c,一下是参数传递的简略示意图
这个指令和 ls -a -l -i 形式是相同的,都是可执行程序带上若干选项

char* argv[0]存储可执行程序的名称
char* argv[1/2/3/%…]存储可执行程序后面接上的选项
argv代表的是argv中有效数据的个数,像上述指令的argv就为4,最后的NULL不算

[clx@VM-20-6-centos test5_Linux]$ cat mytest.c  --循环打印数组中的每一个元素
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[], char *envp[])
{for (int i = 0;i < argc; i++){printf("argv[%d]: %s\n", i, argv[i]);}return 0;
}
[clx@VM-20-6-centos test5_Linux]$ make
gcc -o mytest mytest.c -std=c99
[clx@VM-20-6-centos test5_Linux]$ ./mytest -a -b -c --系统会将./mytest,-a,-b,-c选项传递给main函数
argv[0]: ./mytest
argv[1]: -a
argv[2]: -b
argv[3]: -c

那么如何使用带参的main函数呢

需要使用较新的语法,所以要在Makefile中加上-std=c99
mytest:mytest.cgcc -o $@ $^ -std=c99[clx@VM-20-6-centos test1_Linux]$ cat mytest.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[], char *envp[])
{printf("%d\n", argc);    --打印argcif (argc == 2)           --必须确定argc的范围{if (strcmp(argv[1], "a") == 0){printf("Hello world\n");}else if (strcmp(argv[1], "b") == 0){printf("Hello Linux\n");}else {printf("Hello default\n");}}
}
[clx@VM-20-6-centos test1_Linux]$ ./mytest
1
[clx@VM-20-6-centos test1_Linux]$ ./mytest a
2
Hello world
[clx@VM-20-6-centos test1_Linux]$ ./mytest b
2
Hello Linux

这样就可以通过控制命令参数实现不同的功能了

4.5环境变量具有全局性

但是上述实验并没有使用到最后一个参数envp[],接下来我们看看这个指针数组有什么

逐条打印envp中的数据
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[], char *envp[])
{int i = 0;while (envp[i]){printf("envp[%d]:%s\n", i, envp[i]);i++;}
}
[clx@VM-20-6-centos test1_Linux]$ ./mytest
envp[0]:XDG_SESSION_ID=135944
envp[1]:HOSTNAME=VM-20-6-centos
envp[2]:TERM=xterm
envp[3]:SHELL=/bin/bash
envp[4]:HISTSIZE=3000
envp[5]:SSH_CLIENT=112.12.244.31 8664 22
envp[6]:SSH_TTY=/dev/pts/4
envp[7]:USER=clx
envp[8]:LD_LIBRARY_PATH=:/home/clx/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
envp[9]:LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
envp[10]:MAIL=/var/spool/mail/clx
envp[11]:PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/clx/.local/bin:/home/clx/bin
envp[12]:PWD=/home/clx/Lesson_Linux/lesson_7_23/test1_Linux
envp[13]:TST_HACK_BASH_SESSION_ID=130585844211279
envp[14]:LANG=en_US.utf8
envp[15]:SHLVL=1
envp[16]:HOME=/home/clx
envp[17]:LOGNAME=clx
envp[18]:SSH_CONNECTION=112.12.244.31 8664 10.0.20.6 22
envp[19]:LESSOPEN=||/usr/bin/lesspipe.sh %s
envp[20]:PROMPT_COMMAND=history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"
envp[21]:XDG_RUNTIME_DIR=/run/user/1001
envp[22]:HISTTIMEFORMAT=%F %T
envp[23]:_=./mytest
envp[24]:OLDPWD=/home/clx/Lesson_Linux/lesson_7_23

我们发现envp指向的数据和我们使用env查到的系统的环境变量是一样的
操作系统可以获取环境变量并且传递给我们写的程序

[clx@VM-20-6-centos test1_Linux]$ export MYVAL="you can see me" --加入全局变量MYVAL
[clx@VM-20-6-centos test1_Linux]$ ./mytest | grep MYVAL--我们可以在enpv中找到MYVAL
envp[14]:MYVAL=you can see me
[clx@VM-20-6-centos test1_Linux]$ my="you can see me"  --创建本地变量my
[clx@VM-20-6-centos test1_Linux]$ ./mytest | grep my   --找不到my变量
envp[25]:_=./mytest
[clx@VM-20-6-centos test1_Linux]$ export my         --将my升级成环境变量
[clx@VM-20-6-centos test1_Linux]$ ./mytest |grep my  --可以找到my
envp[19]:my=you can see me
envp[26]:_=./mytest

这说明我们的可执行程序可以实时接收系统传递的环境变量
结论:环境变量是一个系统级别的全局变量,bash之下的所有进程都可以获取,本质是因为main(,enpv[]) 函数拥有第三个参数

4.6获取/修改/删除环境变量的方式

获取:
方法一:使用带参的main函数

#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[], char *envp[])
{int i = 0;while (envp[i]){printf("envp[%d]:%s\n", i, envp[i]);i++;}
}

方法二:使用全局变量 environ
C语言定义了一个二级指针变量指向envp数组的第一个元素

#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[], char *envp[])
{int i = 0;while (envp[i]){printf("envp[%d]:%s\n", i, envp[i]);i++;}
}

方法三:使用getenv()函数
在头文件#include <stdlib.h>中有一个getenv函数
使用方式:getenv(“变量名称”)

[clx@VM-20-6-centos test1_Linux]$ cat mytest.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main()
{printf("%s\n", getenv("PATH"));
}测试:
[clx@VM-20-6-centos test1_Linux]$ ./mytest
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/clx/.local/bin:/home/clx/bin
[clx@VM-20-6-centos test1_Linux]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/clx/.local/bin:/home/clx/bin

方法四:echo 指令
echo $变量名称

[clx@VM-20-6-centos test1_Linux]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/clx/.local/bin:/home/clx/bin

修改:

[clx@VM-20-6-centos test1_Linux]$ export MYVAL=100
[clx@VM-20-6-centos test1_Linux]$ export MYVAL=50
[clx@VM-20-6-centos test1_Linux]$ env | grep MYVAL
MYVAL=50

删除

[clx@VM-20-6-centos test1_Linux]$ unset MYVAL
[clx@VM-20-6-centos test1_Linux]$ env | grep MYVAL
[clx@VM-20-6-centos test1_Linux]$

5.进程地址空间

5.1了解进程地址空间

以下是进程空间的简略示意图

验证地址空间示意图

[clx@VM-20-6-centos test1_Linux]$ cat mytest.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <malloc.h>int g_unval;
int g_val = 100;
int main(int argc, char* argv, char* envp[])
{printf("code addr: %p\n", main);//代码区char* str = "hello world";printf("read only addr: %p\n",str);//常量区 printf("init addr: %p\n",&g_val);//初始化变量printf("uninit addr: %p\n",&g_unval);//未初识化变量int* p =(int*)malloc(sizeof(int));printf("heap addr: %p\n",p);//堆区printf("stack addr: %p\n",&str);//栈区printf("stack addr: %p\n",&p);//栈区printf("argc addr: %p\n", &argc);for(int i = 0; i < argc; i++){printf("argv addr: %p\n",&argv[i]);//命令行参数}int i = 0;while (envp[i]){printf("env addr: %p\n", envp[i]);//环境变量i++;}return 0;
}
[clx@VM-20-6-centos test1_Linux]$ ./mytest
code addr: 0x40057d
read only addr: 0x40077f
init addr: 0x60103c
uninit addr: 0x601044
heap addr: 0x1118010
stack addr: 0x7ffd382c2020
stack addr: 0x7ffd382c2018
argc addr: 0x7ffd382c200c
argv addr: 0x7ffd382c2118
env addr: 0x7ffd382c375b
env addr: 0x7ffd382c3771
env addr: 0x7ffd382c3789
env addr: 0x7ffd382c3794
env addr: 0x7ffd382c37a4
env addr: 0x7ffd382c37b2
env addr: 0x7ffd382c37d2
env addr: 0x7ffd382c37e5
env addr: 0x7ffd382c37ee
env addr: 0x7ffd382c3831
env addr: 0x7ffd382c3dcd
env addr: 0x7ffd382c3de6
env addr: 0x7ffd382c3e40
env addr: 0x7ffd382c3e73
env addr: 0x7ffd382c3e9b
env addr: 0x7ffd382c3eab
env addr: 0x7ffd382c3eb3
env addr: 0x7ffd382c3ec2
env addr: 0x7ffd382c3ece
env addr: 0x7ffd382c3efc
env addr: 0x7ffd382c3f1f
env addr: 0x7ffd382c3f85
env addr: 0x7ffd382c3fa4
env addr: 0x7ffd382c3fba
env addr: 0x7ffd382c3fc5

5.2进程地址空间的作用

先做一个小实验感受一下

[clx@VM-20-6-centos test1_Linux]$ cat mytest.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int g_val = 200;                        --定义一个全局变量int main(int argc, char* argv[], char* envp[])
{printf("I'm running ...\n");pid_t id = fork();                    --创建一个子进程if (id == 0){g_val = 100;                        --修改这个全局变量printf("I'm child .. g_val = %d, pg_val = %p,  pid = %d, ppid = %d\n", g_val,&g_val,  getpid(), getppid());}else if (id > 0){printf("I'm father .. g_val = %d, pg_val = %p, pid %d, ppid = %d\n", g_val, &g_val, getpid(), getppid());}else{printf("I'm default\n");}return 0;
}[clx@VM-20-6-centos test1_Linux]$ ./mytest
I'm running ...
I'm father .. g_val = 200, pg_val = 0x601054, pid 12159, ppid = 6448
I'm child .. g_val = 100, pg_val = 0x601054,  pid = 12160, ppid = 12159

通过实验我们发现,全局变量g_val的地址在两个进程中相同,但是g_val的值在两个进程中不同。一个相同的地址存在两个值,这显然是不可能的。

结论
地址相同值不同,该地址绝对不是物理地址

在Linux系统下,这种地址叫做虚拟地址,并且我们在C/C++语言中看到的地址全部都是虚拟地址!物理地址由操作系统(OS)统一管理。同一个变量地址相同,其实只是虚拟地址相同,内容不同是因为被映射到了不同的物理地址

理解了上述结论我们就来看一下实际计算机存储进程数据的示意图

mm_struct:实际就是我们的进程地址空间,如果我们使用的是三十二位机器那么我们就可以想象空间中有2^32个小格子,每个格子都有自己的地址。我们可以根据不同模块的大小,将每个模块的起始地址和结束地址列出来,这样就构成了我们的mm_struct,进程中的数据也就有了自己的虚拟地址,操作系统通过页表将虚拟地址和物理内存形成映射,将我们的数据存入物理内存

在进程创建的过程中还会创造一个名为mm_struct的结构体,PCB(task_struct)中有指针指向这个结构体.,虚拟地址通过和进程空间对照,通过页表映射到物理内存中。而我们呢的子进程在创建时也生成了自己的mm_struct 和 页表因为子进程和和父进程共用代码和数据,所以子进程的mm_struct可以看作是父进程的拷贝。注意注意注意,这里说的是拷贝,其初识化的mm_struct和父进程相同并且经过页表映射指向同一块物理内存。但是初始化你要是再想改变子进程数据,那么映射也会随之改变,页表会将子进程输入的虚拟地址空间映射到另外的区域,开辟一个新的物理地址来储存g_val,所以同一个虚拟地址经过不同的页表映射得到的物理地址可能是不一样的

补充:其实我们的可执行程序(.exe文件)也是分段的(编译器干的),被加载到内存中后,mm_struct会告诉PCB转告操作系统,哪个地方是代码区,哪个地方是字符常量区等等。操作系统直接使用页表对这些块进行映射就可以了

区域扩大: 在我们的实际使用中,可能不同进程的堆、栈、代码区等等大小是不同的,想要扩大区域只需要改变进程生成的mm_struct。比如想要将堆变小让栈变大,我们只需要在mm_struct减小堆的最大地址,然后降低栈的最小地址就可以了。这样我们就能得到更大的栈

重新理解创建进程:新建的数据结构 有 task_struct, mm_struct, 页表

经过上述铺垫,那么为什么要有地址空间?
1.从此以后不会再有错误访问物理内存存在了。
在进程中的任何地址都要进行转化,页表会查看对应的映射关系。假如一个地址没有映射关系想要访问内存(野指针),页表就找不到映射关系,就会判定你为野指针,操作系统直接中止程序,也就是我们所说的程序崩溃

2.更加规范,每个进程都认为看到的是相同的空间范围
即将进程分块,每个进程的每一个部分都占用同样的空间范围,打个比方就是每一户人家都有好多相同的篮子,然后规定第一个篮子只能养兔子,第二个篮子只能养狗等等,这样我们就可以通过空间范围判断其中的数据类型了

3.每个进程都认为自己独占内存,可以更好完成进程的独立性以及合理运用空间
进程调度和内存管理进行解耦和分离!!,因为每个进程都认为自己独占内存,保证了每个进程的独立性。
假如我们想玩一个10GB大小的游戏,但是电脑内存只有4GB。显然我们无法将庞大的游戏全部加载。但是电脑任然可以调度这个程序,是因为调度和内存管理解耦,虽然操作系统调度了这个进程,但是操作系统可以只拷取部分的代码和数据进入物理内存。所以我们**实际所说的进程进入内存指的是虚拟内存,而操作系统只会将马上就要进行计算的数据加载到物理内存当中。**这样操作系统就可以处理比内存大的多的进程并且保证其他进程正常运行。

[结语]:

以上即是【Linux】3.0LInux进程概念的全部内容了,如果对你有帮助的话请点一个赞吧感谢支持

【Linux】3.0Linux进程概念相关推荐

  1. Linux 系统编程 -进程概念篇

    Linux系统编程-进程篇 冯诺依曼体系结构 冯诺依曼的两个重要思想 当代计算机的三级缓存 操作系统 操作系统的概念 操作系统的组成 操作系统作用 Linux下的操作系统体系 进程 进程概念 进程特性 ...

  2. 【linux】:进程概念

    文章目录 冯诺依曼体系结构 一:操作系统 二:  进程 总结 冯诺依曼体系结构 我们常见的计算机,如笔记本.我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系. 冯诺依曼体系如下图: 那么输入设备 ...

  3. 【Linux学习】进程概念

    目录 一.进程的基本概念 二.进程的描述-PCB 三.对进程的理解 1. 查看进程 2. 通过系统调用获取进程标识符 3. 通过系统调用(fork)创建进程 四.进程状态 1.进程状态转换 2. Li ...

  4. Linux系统编程-进程概念、进程管理、信号处理

    1. 进程知识点 操作系统里的进程是程序一次执行的过程,是操作系统动态执行的基本单元:每当创建新的进程后,操作系统会为新的进程分配一个唯一的标识符,方便后续管理进程. 进程的概念主要有两点: 第一,进 ...

  5. 【Linux操作系统】进程概念详解

    目录 冯诺依曼体系结构 操作系统OS 概念 为什么要有OS OS的定位 如何理解管理 系统调用和库函数概念 进程! 概念 PCB 进程VS数据 task_ struct内容分类 通过系统调用创建进程- ...

  6. 【Linux入门】进程概念(超详解,建议收藏)

    目录 1️⃣进程的基本概念 2️⃣描述进程-PCB 3️⃣通过系统调用获取进程标示符 4️⃣通过系统调用创建进程-fork(初识) 5️⃣进程的状态 R 可执行状态 S 睡眠状态 D 磁盘休眠状态 T ...

  7. 【Linux学习】进程概念(上)

  8. 《Linux进程概念,进程创建退出等待替换,环境变量等基础操作 ---总结》

    前言 Linux系统的进程概念,进程状态,以及操作创建进程,还有环境变量及相关指令,程序地址空间等一些知识的梳理,将自己的理解总结如下,希望和各位一起进步,一起学习. 以下是本篇文章正文内容. 文章目 ...

  9. 【Linux 内核】进程管理 ( 内核线程概念 | 内核线程、普通进程、用户线程 | 内核线程与普通进程区别 | 内核线程主要用途 | 内核线程创建函数 kernel_thread 源码 )

    文章目录 一.内核线程概念 二.内核线程.普通进程.用户线程 三.内核线程.普通进程区别 四.内核线程主要用途 五.内核线程创建函数 kernel_thread 源码 一.内核线程概念 直接 由 Li ...

最新文章

  1. 教师课堂教学必备的100个妙招,总有一个适合你!
  2. 《李宏毅机器学习特训营》免费开放!直播教学!
  3. linux清除history历史命令:history -c
  4. oracle 把逗号分隔符,将逗号分隔为Oracle中的列
  5. CF204E-Little Elephant and Strings【广义SAM,线段树合并】
  6. hdu 1250 Hat's Fibonacci
  7. criterions的选择
  8. Micropython教程之TPYBoard DIY金属探测仪实例演示(萝卜学科编程教育)
  9. python如何向服务器发送文件,在Python中使用套接字向服务器发送文件
  10. 最简单的Gif生成工具,ScreenToGif操作指南(一)
  11. AutoCAD2012从入门到精通中文视频教程 第一课 简介及界面组成 (个人收藏)
  12. 街机模拟器 WinKawaks 及街机 ROM 下载
  13. 计算机二级大题知识点汇总,计算机二级office复习知识点「汇总」
  14. leetcode-买卖股票的最佳时机含手续费
  15. win10计算机管理不可用,win10管理员被禁用怎么办,win10怎么管理员运行
  16. 网站背景音乐隐藏按钮自动播放
  17. PIL读入图片转为BGR
  18. 杀不死的人狼——我读《人月神话》(五)
  19. [导入]PSP 经典游戏合集
  20. 牺牲一个存储空间的循环队列实现方法

热门文章

  1. 什么是formData
  2. 中国人为什么喜欢创业?
  3. Android+上百实例源码分析以及开源分析+集合打包
  4. 前端之HTML学习笔记一(B站黑马程序员)
  5. 都挺好 苏大强C位出道的不只表情包 还有大眼袋
  6. 解决vuecli-vue2项目ie浏览器白屏
  7. android动态开场,Android 开场动画
  8. 促使新网站快速增加百度收录的几个不外传技巧
  9. TP框架中S函数使用方法
  10. 面试杂谈:数组去重和时间复杂度