【ONE·Linux || 地址空间与进程控制(一)】

总言

  进程地址空间和进程控制相关介绍。

文章目录

  • 总言
  • 1、进程地址空间
    • 1.1、程序地址空间初识
      • 1.1.1、介绍程序地址空间划分及地址空间初步验证
      • 1.1.2、地址空间再次综述演示
      • 1.1.3、两个补充问题:
    • 1.2、地址空间是什么
      • 1.2.1、阶段认识一:故事引入
      • 1.2.2、阶段认识二:虚拟地址和映射机制
      • 1.2.3、阶段认识三:内核数据结构和相关问题解释
    • 1.3、为什么要有地址空间
      • 1.3.1、理由一
      • 1.3.2、理由二
      • 1.3.3、理由三
      • 1.3.4、关于挂起的理解
  • 2、进程控制
    • 2.1、进程创建
      • 2.1.1、fork
      • 2.1.2、写时拷贝
    • 2.2、进程终止
      • 2.2.1、进程终止时,操作系统做了什么?
      • 2.2.2、进程终止的常见方式?
      • 2.2.3、如何正确的用代码终止一个进程?

  
  

1、进程地址空间

在这里插入图片描述

1.1、程序地址空间初识

1.1.1、介绍程序地址空间划分及地址空间初步验证

在这里插入图片描述
  
  
  1)、验证地址空间分布(进程打印)

  相关代码如下:

#include <stdio.h>
#include <unistd.h>
#include<stdlib.h>int un_g_val;//未初始化全局数据
int g_val=100;//已初始化全局数据int main(void)
{printf("test:%p\n",main);//验证代码区:函数名表示其地址,函数是代码printf("init:%p\n",&g_val);//验证全局区:已初始化全局区printf("unint:%p\n",&un_g_val);//验证全局区:未初始化全局区char*p=(char*)malloc(16);printf("heap:%p\n",p);//验证堆区:malloc在堆上申请空间,将对应地址存储在p变量中,*p指向堆上的地址空间,printf("stack:%p\n",&p);//验证栈区:p变量本身在在main函数(栈)中创建,故&p获得的是栈上的地址。return 0;}

  验证结果如下:
在这里插入图片描述

  
  

  
  2)、验证堆区地址分布(栈区地址分布在上述中已经验证)

[wj@VM-4-3-centos t1109]$ ./proc.out
test:0x40057dinit:0x60103c
unint:0x601044heap1:0xa02010
heap2:0xa02030
heap3:0xa02050
heap4:0xa02070stack1:0x7ffd03d56808
stack2:0x7ffd03d56800
stack3:0x7ffd03d567f8
stack4:0x7ffd03d567f0[wj@VM-4-3-centos t1109]$ 
int un_g_val;//未初始化全局数据
int g_val=100;//已初始化全局数据int main(void)
{printf("test:%p\n",main);//验证代码区:函数名表示其地址,函数是代码printf("init:%p\n",&g_val);//验证全局区·已初始化全局区printf("unint:%p\n",&un_g_val);//验证全局区·未初始化全局区char*p1=(char*)malloc(16);//动态开辟:开辟的空间在堆上char*p2=(char*)malloc(16);//动态开辟:开辟的空间在堆上char*p3=(char*)malloc(16);//动态开辟:开辟的空间在堆上char*p4=(char*)malloc(16);//动态开辟:开辟的空间在堆上printf("heap1:%p\n",p1);//验证堆区:P在堆上,*p是指向开辟的堆上地址空间的指针变量,在main函数(栈)中创建,处于栈上。printf("heap2:%p\n",p2);//验证堆区:P在堆上,*p是指向开辟的堆上地址空间的指针变量,在main函数(栈)中创建,处于栈上。printf("heap3:%p\n",p3);//验证堆区:P在堆上,*p是指向开辟的堆上地址空间的指针变量,在main函数(栈)中创建,处于栈上。printf("heap4:%p\n",p4);//验证堆区:P在堆上,*p是指向开辟的堆上地址空间的指针变量,在main函数(栈)中创建,处于栈上。printf("stack1:%p\n",&p1);printf("stack2:%p\n",&p2);printf("stack3:%p\n",&p3);printf("stack4:%p\n",&p4);return 0;}

  
  
  3)、上述提到的地址空间是内存吗?
  回答:这里所说的地址空间不是内存。
  延伸:既然这里的地址空间不是内存,那么它是什么?
  

演示实例一:

  相关代码:

#include<stdio.h>
#include<unistd.h>int g_val=100;int main(void)
{pid_t id=fork();if(id==0){int cnt=0;while(1){printf("I am child  , pid:%d, ppid:%d, g_val:%d, &g_val:%p\n",getpid(),getppid(),g_val,&g_val);sleep(1);cnt++;if(cnt==5){g_val=200;printf("child changed g_val : 100->200 success\n");}}}else {while(1){printf("I am parent , pid:%d, ppid:%d, g_val:%d, &g_val:%p\n",getpid(),getppid(),g_val,&g_val);sleep(1);}}return 0;}

  运行结果:

[wj@VM-4-3-centos t1109]$ ./proc.out
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
I am child  , pid:14258, ppid:14257, g_val:100, &g_val:0x60105c
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
I am child  , pid:14258, ppid:14257, g_val:100, &g_val:0x60105c
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
I am child  , pid:14258, ppid:14257, g_val:100, &g_val:0x60105c
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
I am child  , pid:14258, ppid:14257, g_val:100, &g_val:0x60105c
I am child  , pid:14258, ppid:14257, g_val:100, &g_val:0x60105c
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
child changed g_val : 100->200 success
I am child  , pid:14258, ppid:14257, g_val:200, &g_val:0x60105c
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
I am child  , pid:14258, ppid:14257, g_val:200, &g_val:0x60105c
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
I am child  , pid:14258, ppid:14257, g_val:200, &g_val:0x60105c
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
I am child  , pid:14258, ppid:14257, g_val:200, &g_val:0x60105c
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
I am child  , pid:14258, ppid:14257, g_val:200, &g_val:0x60105c
I am parent , pid:14257, ppid:17613, g_val:100, &g_val:0x60105c
^C
[wj@VM-4-3-centos t1109]$ 

  
在这里插入图片描述
  前提先知:
  0、fork之后,代码父子进程共享,那么变量g_val父子进程也要同时共享
  观察现象:
  1、子进程改变全局变量前:二者g_val值相同,&g_val地址值相同
  2、子进程改变全局变量后:二者g_val值不同,&g_val地址值相同
  分析:
  1、进程具有独立性,子进程修改了g_val值,不会影响父进程的g_val值。符合之前所学内容。
  2、同一个地址,同时读取的时候,出现了不同的值。只能说明这里的地址,绝对不可能是物理内存的地址
  
  延伸: 如果这里的地址不是物理内存的地址,那么&取地址得到的是什么?
  回答:虚拟地址(线性地址)
  1、几乎所有语言,如果有“地址”的概念,这个地址一定不是物理地址,而是虚拟地址。
  
  
  

1.1.2、地址空间再次综述演示

  1)、代码举例
  以下为验证代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_unval;
int g_val = 100;int main(int argc, char *argv[], char *env[])
{//int a = 10;//字面常量//const char *str = "helloworld";// 10;// 'a';printf("code addr: %p\n", main);printf("init global addr: %p\n", &g_val);printf("uninit global addr: %p\n", &g_unval);   printf("\n");int test1 = 10;static int test2 = 10;                                       char *heap_mem = (char*)malloc(10);char *heap_mem1 = (char*)malloc(10);char *heap_mem2 = (char*)malloc(10);char *heap_mem3 = (char*)malloc(10);printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1) printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)printf("heap addr: %p\n", heap_mem2); //heap_mem(0), &heap_mem(1)printf("heap addr: %p\n", heap_mem3); //heap_mem(0), &heap_mem(1)   printf("\n");printf("test1 addr: %p\n", &test1); printf("test2 stack addr: %p\n", &test2); printf("\n");printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem2); //heap_mem(0), &heap_mem(1)printf("stack addr: %p\n", &heap_mem3); //heap_mem(0), &heap_mem(1)printf("\n");//printf("read only string addr: %p\n", str);int i=0;for( i = 0 ;i < argc; i++){printf("argv[%d]: %p\n", i, argv[i]);}for( i = 0; env[i]; i++){printf("env[%d]: %p\n", i, env[i]);}return 0;
}

  
  
  2)、对堆栈地址的说明
  如图示:
在这里插入图片描述

  1、堆区和栈区地址之间差距很大的原因?
    中间间隔了共享区。

  2、堆区malloc开辟出来的地址相差值?(即我们只开辟了10字节,为什么这里显示的值更多?)
    一个问题:free只传入空间起始地址,其怎么知道需要释放多少空间
    回答:malloc多申请了一些空间地址,以用来记录当前malloc出来的属性信息。
  
  
  
  
  3)、对static定义的局部变量的说明
  如图示:
在这里插入图片描述

  问题:为什么从操作系统角度理解static定义局部变量,其作用域在当前函数有效,生命周期却具有全局属性?
  回答:被static修饰的局部变量,其地址空间到了全局数据区。(之前我们只是在语言角度上理解,这里给出了实际验证)
  
  
  
  
  4)、对字面常量的说明
  问题:如图示,直接输入字面常量,是否能编译通过?
在这里插入图片描述
  回答:图示状态能编译通过,因为它们都是字面常量。比如:int a =10a是变量,10是字面常量,是把字面常量值放入了变量a里。
  
  以下为是字面常量所处地址空间的相关验证:
在这里插入图片描述

  问题:给字符串常量赋一个指向它的指针,该字符串常量其地址空间在哪?(对字符串常量区的理解)
  回答:实际上这些字面常量,是硬编码进代码的。代码区中有一块小区域,是字符常量区,是只读的。
  
  
  

1.1.3、两个补充问题:

  1)、用户空间 vs 内核空间
  1、32位下,一个进程的地址空间,取值范围是0X 0000 0000~ 0X FFFF FFFF。
  2、其中,[0,3GB]为用户空间,[3GB,4GB]为内核空间。
  3、地址空间=用户空间+内核空间。
  
  
  2)、Liunx vs Windows
  1、上述的验证在windows下结果不尽相同。默认上述结论在Linux下有效。
  
  

1.2、地址空间是什么

1.2.1、阶段认识一:故事引入

阶段认识一:

在这里插入图片描述
  
  

1.2.2、阶段认识二:虚拟地址和映射机制

阶段认识二:

  1、历史上曾有直接访问物理内存的情况,由于内存本身可以随时读写,会存在非法地址的现象。
  2、出于安全考虑,引入 虚拟地址、映射机制。虽然最终还是会访问到物理地址上,但映射机制存在检测访问是否非法并拒接映射的能力。
在这里插入图片描述

  
  

1.2.3、阶段认识三:内核数据结构和相关问题解释

阶段认识三:

  1)、虚拟地址空间究竟是什么?
  1、问:地址空间内部存在众多区域,如何理解区域划分?
  答:区域划分本质上是在一个范围内定义出结构体变量,用start\end表示其范围。
  举例如下:

struct addr_room
{//代码区int code_start;int code_end;//已初始化全局区int init_start;int init_end;//未初始化全局区int uninit_start;int uninit_end;//堆区int heap_start;int heap_end;//栈区int stack_start;int stack_end;……//其它属性}

  结论1地址空间是一种内核数据结构,其里面至少要有各个区域的划分。
  需要注意的是,每个区域的划分并不是固定一成不变的,就像栈区向下增长,堆区向上增长,实际上就是改变start、end指向范围,从而改变区域范围。
  
  
  
  2、问:页表,映射机制的表现形式。(该概念后续学习)
  结论2: 地址空间和页表(用户级)是每个进程都私有一份。只要保证每个进程的页表映射的是物理内存的不同区域,就能做到进程之间不互相干扰,从而保证进程的独立性。
在这里插入图片描述
  
  
  
  3、问题: 之前的演示实例一中,为什么地址一样,而值不一样?

在这里插入图片描述

  地址一样: 父子进程在虚拟地址存放g_val变量的位置一致(因为子进程是根据父进程拷贝的来)
  值一样: 通过页表投射到物理内存时,子进程会出现写时拷贝(后续内容)现象,最终结果是父子进程在物理内存中存储位置不一样,从而保证进程独立性。
  总结: 同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址。
  
  相同问题: pid _t ret=fork();fork内部return执行两次。为什么ret能保存不同的两个返回值?
  
  
  
  
  扩展:
  1、当我们的程序编译形成可执行程序但没有运行的时候(此时还没有被加载到内存中),请问,我们的程序内部有地址吗?
  验证如下:可执行程序在编译的时候内部已经有地址了。
在这里插入图片描述
  
  
  2、根据上述需要明白的是,对地址空间,其不仅仅对OS内部适用,对编译器也同样适用。编译器在编译代码时,便是按照地址空间分布分形成各个区域,比如,在Linux下编译程序,就是采用与Linux内核一致的编址方式,对每个变量、每行代码进程编址。因此,程序在编译的时候,其每个字段已经形成了一个虚拟地址。
  
在这里插入图片描述

  
  
  
  2)、映射关系的维护是谁来做的?
  
  
  
  

1.3、为什么要有地址空间

1.3.1、理由一

  1)、理由一:
  1、凡是非法的访问或者映射,OS都会识别到并终止相关进程(即通过拦截动作有效地保护物理内存)。

int main()
{char *str="hello world!";char *s='H'//error;

  如上述例子,实际中物理内存是不会对写入数据进行检测或限制。只不过由于地址空间和页表是OS维护的,我们可以在软件层面让OS对其进行监管,当代码在语言层面上出现问题时,是系统层面上进程被OS杀掉。
  此处存在两个问题:
  1、操作系统如何识别错误?
  2、操作系统如何终止错误进程?
  
  
  

1.3.2、理由二

  2)、理由二:
  问题1、有了地址空间和页表映射的存在,在物理内存中,对于未来的数据,请问,我们是否可以进行任意位置的加载?
  回答: 是。页表能通过映射关系一一找到。
  结论: 这样一来,物理内存的分配和进程管理就能做到没有关联性。即内存管理模块和进程管理模块可以实现解耦合
  
  
  
  问题2、我们在C、C++语言上malloc、new一块空间时,是在哪里申请的呢?
  回答: 在虚拟地址空间。
  
  
  问题3、如果我们申请了物理空间而不立马使用,是不是对空间的浪费呢?
  回答: 是的。
  结论: 因为有地址空间的存在,上层申请空间实际上是在地址空间申请的,而真正的物理内存并没有被分配出去。只有当你真正需要对物理地址空间访问的时候,操作系统才会执行内存相关的管理算法,进行内存申请和页表映射关系的构建。这种相当于延迟分配的过程是由操作系统自动完成的,目标是提高内存使用效率从而提高整机效率,而对于用户、对于进程,二者都出于零感知状态,只是自然而然地进行内存访问。
  此处涉及一个缺页中断的概念。
  
  

1.3.3、理由三

  3)、理由三:
  1、物理内存在理论上可以任意位置加载,就意味着物理地址中几乎所有数据和代码在其内部是乱序的。但是因为有了页表的存在,它可以将虚拟地址空间和物理地址映射,所以,在进程的视角,内存分布是有序的
  2、操作系统既能够让物理内存延迟分配,那么同样也能够让进程映射到不同的物理内存中,从而实现进程的独立性,让每个进程都认为自己拥有的是4G(32位)空间,并且各个区域都是有序的。站在进程的角度,每个进程都不知道也不需要知道其它进程的存在
  
  
  
  

1.3.4、关于挂起的理解

  4)、重新理解什么是挂起:
   1、加载程序的本质就是创建进程,那么是不是说必须立马将程序内的所有代码数据都加载到内存中并建立映射关系呢?
  回答:不需要。(比如,大型游戏120G,OS内存只有8G、16G,它不可能一次性就将其全部加载)
  

  2、基于此,在一些情况下,加载创建进程时,只有其内核结构被创建出来了。这时期的进程状态就称之为新建
  
  3、基于此,从理论上讲,OS能实现对程序的分批加载(换入),那么同样也能实现对程序的分批换出。那么,当进程短时间内不会再被执行时,OS就可以将相关进程的数据和代码换出,就称之为挂起
  
  延伸:页表映射的时候,能够映射的不仅仅是内存,磁盘中的位置也可以映射。(那么在挂起时,也可以不进行数据换入换出,在页表处直接记录个磁盘位置,之后换入时找到相关位置即可)。
  
  
  
  
  

2、进程控制

在这里插入图片描述

2.1、进程创建

2.1.1、fork

  1)、请描述一下,fork创建子进程时,操作系统都做了什么?
  前提先知:进程=内核数据结构(由操作系统维护)+进程代码和数据(一般由磁盘得来,即C/C++程序加载之后的结果)。
  简述性回复:创建子进程,即操作系统中多了一个进程,相应的需要为其带上对应的PCB结构体、对应的虚拟地址空间、页表,并将其代码数据加载到物理内存中构建映射关系。然后将该进程PCB放入到CPU调度队列里,等待进程调度。
  
  
  
  
  2)、fork的最基本使用我们在进程概念章节有所讲述,此处只是简单提及。
在这里插入图片描述
  
  
  
  
  3)、问题:使用fork之后父子进程代码共享。是共享fork之后的代码,还是共享所有的代码?
  回复:共享所有的代码。
  
  问题:既如此,为什么子进程只执行fork后的代码而不执行整体共享的代码?
  回答:
  1、我们的代码在汇编之后,会有很多行代码,每行代码被加载到内存后,有其对应的地址。
  2、根据CPU对进程的调度方式,进程随时可能会被中断,等它在下一次回来再被执行时,需要从原先执行位置之后接着继续执行(而非又从头开始)
  3、因此需要CPU随时记录下当前进程执行的位置,所有CPU内有对应的寄存器数据(一般是EIP,即PC指针,也称程序计数器,用于记录当前执行代码的下一行代码的地址),用来记录当前进程的执行位置。
  4、根据之前我们所学,寄存器在CPU内只有一套,但寄存器内可记录多份进程数据(进程上下文的内容),而进程上下文数据也是进程的一部分。
  5、因此,在创建子进程时,也要将该数据拷贝给子进程一份。虽然之后父子进程各自调度,各自会修改EIP,但子进程已经认为自己的EIP起始值就是fork之后的代码。
  
  一个小实验:main函数递归。
  
  
  

2.1.2、写时拷贝

  1)、关于为什么需要采用写时拷贝技术的问题引入
  需要知道的内容:
  1、创建子进程时,要给子进程分配对应的内核结构,以保障进程独立性。但事实上,一般情况下我们创建子进程时,没有加载的过程,因此子进程也就没有自己的代码和数据
  2、因此,子进程使用的是父进程的代码和数据。

  推进:
  1、对于代码,其只读不写,所以父子共享没问题。
  2、对于数据,其能够被修改,因此需要分离。

  推进:既然父子进程的数据需要分离处理,那么可以怎么分离呢?

  方法一:在创建子进程时,就将父子进程的数据直接拷贝分离。
  存在问题:为子进程拷贝的这个数据空间,可能后续用不到,或者用到了也只是读取数据而不做修改。
  改进:创建子进程时,对于那些不需要立马访问的数据或者只是读取的数据,就不进行拷贝,直接使用父进程的数据。

  推进:那么什么样的数据值得拷贝?
  回答:将来会被父进程或子进程写入的数据。
  问题:
  1、一般而言,OS如何提前知道空间内的哪些数据需要被写入?
  2、就算提前拷贝了,能保证立马使用这些数据而不造成OS空间浪费吗?

  基于上述情况,OS选择写时拷贝技术来将父子进程分离。
  
  方法二:写时拷贝
  1、用时再分配,是高效使用内存的一种表现
  2、OS无法在代码执行前预知那些空间会被访问。
在这里插入图片描述
  
  
  
  

2.2、进程终止

2.2.1、进程终止时,操作系统做了什么?

  1、进程终止时,操作系统要释放相关进程的内核数据结构以及对应的数据和代码。本质上是操作系统在释放系统资源(主要是CPU内存)。
  
  
  
  

2.2.2、进程终止的常见方式?

  1)、进程终止的常见三情形

情形一:代码跑完,结果正确
情形二:代码跑完,结果不正确
情形三:代码没有跑完,程序崩溃(相关重点在信号部分涉及)

  关于一、二,我们以下述场景来分析
  
  
  2)、进程退出码

在这里插入图片描述一个问题:main函数的返回值具有什么意义?return 0的含义是什么?为什么总是返回0?

  回答:
  1、main函数的返回值是进程退出码。其将数值返回给上一级进程(比如父进程),用来评判该进程执行结果。
  2、通常情况下,main函数返回0是表示success非0表示运行结果不正确
  3、非零数值有无数个,因此不同的非零数值就可以标识不同的错误原因。这样当程序运行结束结果不正确时,我们可以很方便的定义错误原因。
  
  
  

在这里插入图片描述如何查看最近一次执行完毕的退出码?

  相关指令:echo $? :获取最近一个进程执行完毕的退出码

[wj@VM-4-3-centos t1113]$ make
gcc -o test.out test.c
[wj@VM-4-3-centos t1113]$ ls
Makefile  test.c  test.out
[wj@VM-4-3-centos t1113]$ ./test.out
hello vim!
[wj@VM-4-3-centos t1113]$ cat test.c //这是Test.c的内容
#include<stdio.h>int main()
{printf("hello vim!\n");return 10; //我们将返回值设置为10
}
[wj@VM-4-3-centos t1113]$ echo $? //执行该条指令可看到最近一次进程(即main函数)的退出码是10.
10
[wj@VM-4-3-centos t1113]$ echo $? //再次执行结果为0是因此这里显示的是上一个echo $?的结果
0
[wj@VM-4-3-centos t1113]$ echo $?
0
[wj@VM-4-3-centos t1113]$ 

  
  
  

在这里插入图片描述如何获取退出码,将其转换为我们认识的错误信息?

  相关函数:strerror,将系统对应的错误码/退出码转换为字符串描述
  相关代码演示及结果

#include<stdio.h>
#include<string.h>int main()
{int i=0;for(i=0;i<150;i++){printf("number%d:%s\n",i,strerror(i));}return 0;
}

  number134:Unknown error 134,到134后就没有了。

[wj@VM-4-3-centos t1113]$ ./test.out
number0:Success
number1:Operation not permitted
number2:No such file or directory
number3:No such process
number4:Interrupted system call
number5:Input/output error
number6:No such device or address
number7:Argument list too long
number8:Exec format error
number9:Bad file descriptor
number10:No child processes
number11:Resource temporarily unavailable
number12:Cannot allocate memory
number13:Permission denied
number14:Bad address
number15:Block device required
number16:Device or resource busy
number17:File exists
number18:Invalid cross-device link
number19:No such device
number20:Not a directory
number21:Is a directory
number22:Invalid argument
number23:Too many open files in system
number24:Too many open files
number25:Inappropriate ioctl for device
number26:Text file busy
number27:File too large
number28:No space left on device
number29:Illegal seek
number30:Read-only file system
number31:Too many links
number32:Broken pipe
number33:Numerical argument out of domain
number34:Numerical result out of range
number35:Resource deadlock avoided
number36:File name too long
number37:No locks available
number38:Function not implemented
number39:Directory not empty
number40:Too many levels of symbolic links
number41:Unknown error 41
number42:No message of desired type
number43:Identifier removed
number44:Channel number out of range
number45:Level 2 not synchronized
number46:Level 3 halted
number47:Level 3 reset
number48:Link number out of range
number49:Protocol driver not attached
number50:No CSI structure available
number51:Level 2 halted
number52:Invalid exchange
number53:Invalid request descriptor
number54:Exchange full
number55:No anode
number56:Invalid request code
number57:Invalid slot
number58:Unknown error 58
number59:Bad font file format
number60:Device not a stream
number61:No data available
number62:Timer expired
number63:Out of streams resources
number64:Machine is not on the network
number65:Package not installed
number66:Object is remote
number67:Link has been severed
number68:Advertise error
number69:Srmount error
number70:Communication error on send
number71:Protocol error
number72:Multihop attempted
number73:RFS specific error
number74:Bad message
number75:Value too large for defined data type
number76:Name not unique on network
number77:File descriptor in bad state
number78:Remote address changed
number79:Can not access a needed shared library
number80:Accessing a corrupted shared library
number81:.lib section in a.out corrupted
number82:Attempting to link in too many shared libraries
number83:Cannot exec a shared library directly
number84:Invalid or incomplete multibyte or wide character
number85:Interrupted system call should be restarted
number86:Streams pipe error
number87:Too many users
number88:Socket operation on non-socket
number89:Destination address required
number90:Message too long
number91:Protocol wrong type for socket
number92:Protocol not available
number93:Protocol not supported
number94:Socket type not supported
number95:Operation not supported
number96:Protocol family not supported
number97:Address family not supported by protocol
number98:Address already in use
number99:Cannot assign requested address
number100:Network is down
number101:Network is unreachable
number102:Network dropped connection on reset
number103:Software caused connection abort
number104:Connection reset by peer
number105:No buffer space available
number106:Transport endpoint is already connected
number107:Transport endpoint is not connected
number108:Cannot send after transport endpoint shutdown
number109:Too many references: cannot splice
number110:Connection timed out
number111:Connection refused
number112:Host is down
number113:No route to host
number114:Operation already in progress
number115:Operation now in progress
number116:Stale file handle
number117:Structure needs cleaning
number118:Not a XENIX named type file
number119:No XENIX semaphores available
number120:Is a named type file
number121:Remote I/O error
number122:Disk quota exceeded
number123:No medium found
number124:Wrong medium type
number125:Operation canceled
number126:Required key not available
number127:Key has expired
number128:Key has been revoked
number129:Key was rejected by service
number130:Owner died
number131:State not recoverable
number132:Operation not possible due to RF-kill
number133:Memory page has hardware error
number134:Unknown error 134
number135:Unknown error 135
number136:Unknown error 136
number137:Unknown error 137
number138:Unknown error 138
number139:Unknown error 139
number140:Unknown error 140
number141:Unknown error 141
number142:Unknown error 142
number143:Unknown error 143
number144:Unknown error 144
number145:Unknown error 145
number146:Unknown error 146
number147:Unknown error 147
number148:Unknown error 148
number149:Unknown error 149
[wj@VM-4-3-centos t1113]$ 

  我们可以自己使用上述这些退出码,也可以自己设计一套退出方案。

[wj@VM-4-3-centos t1113]$ ls
Makefile  test.c  test.out
[wj@VM-4-3-centos t1113]$ ls aaaa.c  //让ls执行一条不存在的命令
ls: cannot access aaaa.c: No such file or directory  //ls的返回结果,根据上述显示可知其是错误信息中的一条
[wj@VM-4-3-centos t1113]$ echo $?  //查看退出码验证确实如此。
2
[wj@VM-4-3-centos t1113]$ 

  
  

在这里插入图片描述程序崩溃与退出码

  1、程序崩溃时,退出码是无意义的。因为一般情况下退出码对应的return语句没有被执行。

#include<stdio.h>
#include<string.h>int main()
{printf("hello vim1!\n");printf("hello vim2!\n");printf("hello vim3!\n");int *p=NULL;*p=223344;printf("hello vim4!\n");printf("hello vim5!\n");printf("hello vim6!\n");printf("hello vim7\n");return 0;
}
[wj@VM-4-3-centos t1113]$ ./test.out
hello vim1!
hello vim2!
hello vim3!
Segmentation fault //执行结果:可看到第三行打印后就报错。
[wj@VM-4-3-centos t1113]$

  
  

2.2.3、如何正确的用代码终止一个进程?

在这里插入图片描述方法一:main函数中的return语句能够终止进程,return + 退出码即可终止进程。

#include<stdio.h>
#include<string.h>int add(int sum)
{int i=0;int result=0;for(i=0;i<sum;i++){result+=i;}return result;
}int main()
{printf("hello vim1!\n");printf("hello vim2!\n");printf("hello vim3!\n");int sum=add(100);return 11;//int *p=NULL;//*p=223344;printf("hello vim4!\n");printf("hello vim5!\n");printf("hello vim6!\n");printf("hello vim7\n");return 10;
}
[wj@VM-4-3-centos t1113]$ make
gcc -o test.out test.c
[wj@VM-4-3-centos t1113]$ ./test.out
hello vim1!
hello vim2!
hello vim3!
[wj@VM-4-3-centos t1113]$ echo $? //可看到退出码为11
11
[wj@VM-4-3-centos t1113]$ 

  需要注意的是,此处的return语句需要是main函数中的return语句才起效。比如上述代码中的sum返回了return,但其不是进程终止而是函数返回。
  
  

在这里插入图片描述方法二:exit在代码任何地方调用,都能终止进程。

在这里插入图片描述

  演示实例一:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int add(int sum)
{int i=0;int result=0;for(i=0;i<sum;i++){result+=i;}return result;
}int main()
{printf("hello vim1!\n");printf("hello vim2!\n");printf("hello vim3!\n");    int sum=add(100);  exit(111); //在main函数中调用exitprintf("hello vim4!\n");printf("hello vim5!\n");printf("hello vim6!\n");printf("hello vim7\n");return 10;
}
[wj@VM-4-3-centos t1113]$ ./test.out
hello vim1!
hello vim2!
hello vim3!
[wj@VM-4-3-centos t1113]$ echo $?
111
[wj@VM-4-3-centos t1113]$ 

  演示实例二:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int add(int sum)
{int i=0;int result=0;for(i=0;i<sum;i++){result+=i;}exit(222);// return result;
}int main()
{printf("hello vim1!\n");printf("hello vim2!\n");printf("hello vim3!\n");int sum=add(100);//在add函数内置exit    exit(111);printf("hello vim4!\n");printf("hello vim5!\n");printf("hello vim6!\n");printf("hello vim7\n");return 10;
}
[wj@VM-4-3-centos t1113]$ ./test.out
hello vim1!
hello vim2!
hello vim3!
[wj@VM-4-3-centos t1113]$ echo $?
222
[wj@VM-4-3-centos t1113]$ 

  
  

在这里插入图片描述方法三:_exit系统调用

在这里插入图片描述
  演示实例一:常规使用方法
  仍旧用上述代码进行实验,只是把exit换为了_exit,可以看到情况相同。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>int add(int sum)
{int i=0;int result=0;for(i=0;i<sum;i++){result+=i;}_exit(222);//exit(222);// return result;
}int main()
{printf("hello vim1!\n");printf("hello vim2!\n");printf("hello vim3!\n");    int sum=add(100);    _exit(111);//exit(111);//return 11;printf("hello vim4!\n");printf("hello vim5!\n");printf("hello vim6!\n");printf("hello vim7\n");return 10;
}

  
  演示实例二:_exit与exit的区别点

[wj@VM-4-3-centos t1113]$ cat test.c
#include<stdio.h>
#include<unistd.h>int main()
{printf("you can see me.\n");sleep(3);return 11;
}
[wj@VM-4-3-centos t1113]$ ./test.out //在之前学进度条时我们谈论过缓冲区和换行符的问题
you can see me.  //此时演示结果为:先打印字段再睡眠三秒结束。
[wj@VM-4-3-centos t1113]$ echo $?
11
[wj@VM-4-3-centos t1113]$

  将return换为exit函数。

[wj@VM-4-3-centos t1113]$ cat test.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{printf("you can see me.");//去掉\nsleep(3);exit(11);//return 11;
}
[wj@VM-4-3-centos t1113]$ ./test.out
you can see me.[wj@VM-4-3-centos t1113]$ ls  
Makefile  test01.c  test.c  test.out
[wj@VM-4-3-centos t1113]$ ./test.out
you can see me.[wj@VM-4-3-centos t1113]$ echo $? //现象:进程睡眠三秒,然后打印
11

  将exit换为_exit函数。

[wj@VM-4-3-centos t1113]$ cat test.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{printf("you can see me.");sleep(3);_exit(11);//return 11;
}
[wj@VM-4-3-centos t1113]$ make
gcc -o test.out test.c
[wj@VM-4-3-centos t1113]$ ./test.out  //现象:程序运行结束,但输出结果没有被刷新出来。
[wj@VM-4-3-centos t1113]$ echo $?
11
[wj@VM-4-3-centos t1113]$ ./test.out
[wj@VM-4-3-centos t1113]$ echo $?
11
[wj@VM-4-3-centos t1113]$ 

在这里插入图片描述

  
  后续:地址空间与进程控制(二)
  
  
  
  
  
  
  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/21708.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot乒乓球预约管理系统

开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven…

es下载历史的tar文件

第一步进入官网找到历史版本 第二步复制历史版本名称组合成下面的链接 直接get访问下载。如下链接所示只需要修改7.3.0这个版本号 https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.3.0-linux-x86_64.tar.gz

刚体三维运动学【旋转矩阵】【欧拉角】【四元素】

一些概念 轴角法、旋转矩阵、欧拉角、四元数主要用于&#xff1a;向量的旋转、坐标系之间的转换、角位移的计算、方位的平滑插值计算。坐标系的旋转一共有三种表示方法&#xff1a;旋转矩阵、欧拉角和四元数。一般指地面系&#xff08;世界系&#xff09;和机体系之间的旋转关…

Linux —— 进程管理

目录 一&#xff0c;进程介绍 二&#xff0c;进程使用 进程查看 通过系统调用获取进程标识符 通过系统调用创建进程 fork 一&#xff0c;进程介绍 进程是正在执行的程序或命令&#xff0c;每个进程都是一个运行的实体或程序的执行实例&#xff0c;有自己的地址空间&#x…

【Excel】excel多个单元格的内容合并到一个单元格,并使用分隔符

方法一&#xff1a;使用连接符 & 左键单击选中“D2”单元格&#xff0c;在D2单元格中输入公式“A2&B2&C2”&#xff0c;按“Enter”即可实现数据合并。 ------如果想连接的时候&#xff0c;中间加分隔符&#xff0c;可以使用&#xff1a;公式A2&"&#xf…

FPGA学习——PWM实现呼吸流水灯(附源码)

文章目录 一、PWM简介1.1 PWM定义1.2 PWM参数 二、Verilog实现PWM呼吸灯三、实现效果四、总结 一、PWM简介 1.1 PWM定义 PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用&#xff0c;方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM信号…

AI Is the New Power

这个题目纯粹是为了博眼球&#xff0c;因为吴恩达有个题目是AI Is the New Electricity。&#xff1a;&#xff09;但是我想AI确实是为我们这些企业信息化顾问顾问赋予了新的力量&#xff0c;在我们的职业生涯中开辟了新的可能性。 在几周前的文章中&#xff0c;我们提到“终点…

Harnessing the Power of LLMs in Practice: A Survey on ChatGPT and Beyond

LLM的系列文章&#xff0c;针对《Harnessing the Power of LLMs in Practice: A Survey on ChatGPT and Beyond》的翻译。 在实践中驾驭LLM的力量——ChatGPT及其后的研究综述 摘要1 引言2 模型实用指南2.1 BERT风格的语言模型&#xff1a;编码器-解码器或仅编码器2.2 GPT风格…

WEIQ自动登录实现

文章目录 声明目标网址password加密分析代码实现声明 本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请私信我立即删除! 目标网址 aHR0cHM6Ly93d3cud2VpcS5jb20vpassword加密分析 研究一下登录加密逻辑,随便…

js小写金额转大写 自动转换

// 小写转为大写convertCurrency(money) {var cnNums [零, 壹, 贰, 叁, 肆, 伍, 陆, 柒, 捌, 玖]var cnIntRadice [, 拾, 佰, 仟]var cnIntUnits [, 万, 亿, 兆]var cnDecUnits [角, 分, 毫, 厘]// var cnInteger 整var cnIntLast 元var maxNum 999999999999999.9999var…

基于springboot+Redis的前后端分离项目(七)-【黑马点评】

&#x1f381;&#x1f381;资源文件分享 链接&#xff1a;https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11 提取码&#xff1a;eh11 发布笔记&#xff0c;点赞&#xff0c;点赞排行 达人探店1、达人探店-发布探店笔记2、 达人探店-查看探店笔记3、 达人探店-点赞功…

从零搭建ros间的通信,各功能包、节点之间的通信

新建消息类型 catkin_create_pkg car_interfaces roscpp rospy std_msgs message_generation message_runtime书写自定义的msg&#xff1a; 比如我写一个GlobalPathPlanningInterface.msg&#xff1a; float64 timestamp #时间戳 float32[] startpoint #起点位置&#x…