(四)进程环境

Table of Contents

0.那么进程所需的运行环境有哪些?

1. 启动代码 

1.1 启动代码的作用

1.2 启动代码是由谁提供的

(1)启动代码一般都是由编译器提供的,一般有两种提供方式

(2)gcc -v

1.3 启动代码做了些什么 

1.3.1 启动代码使用什么语言编写的

1.3.2 启动代码大致做了些什么呢? 

1.4 程序是如何运行起来的

1.4.1 裸机的情况 

1.4.1 有OS的情况  

2. 进程(程序)的终止方式

2.1 正常终止

1)main函数调用return关键字

2)在程序的任意位置调用exit函数 

3)在程序的任意位置调用_exit函数 

4)return、exit和_exit的返回值问题

2.2、异常终止

2.3 atexit函数

2.4 有OS时,进程从启动 到 正常终止的全过程

2.4.1 图示

2.4.2 为什么调用exit、return正常终止时,会刷新标准io的缓存

2.5 命令行参数

3. 环境变量表    

3.1 windows的环境变量    

3.2 环境变量表

3.3 Linux的环境变量

 修改Linux的环境变量表

1)如何使用命令来修改“命令行窗口进程”的环境变量表

2)通过API修改环境变量

4. c程序内存空间布局

1)什么是c程序的内存空间

2)c程序的内存空间结构

5. 库    


0.那么进程所需的运行环境有哪些?

所需环境有:启动代码、环境变量、c程序的内存空间布局、库等。

1. 启动代码 

1.1 启动代码的作用

所有高级语言的程序,都有自己的启动代码。

C程序运行时,最开始运行的是启动代码,启动代码再去调用main函数,然后整个C程序都已运行。

                                                     

总之,高级语言程序 = 启动代码 + 自己代码。

1.2 启动代码是由谁提供的

(1)启动代码一般都是由编译器提供的,一般有两种提供方式

1)源码形式

     以源码形式提供时,编译器会将启动代码的源文件和自己程序的源文件一起编译。

     

     像开发单片机这种没有OS的计算机的C程序时,启动代码一般是源码形式提供

2)二级制的.o(目标文件)形式

    直接以.o形式提供时,省去了我自己对“启动代码”的编译。

     

     如果开发的程序是运行在OS上时,那么编译器一般是以.o形式来提供启动代码

     比如我们gcc a.c时,gcc就是以.o形式提供的,基于OS运行的程序的启动代码,相对而言,自然比较复杂些。

 

gcc时加一个-v选项,查看gcc编译链接的详细情况时,可以看到有很多.o,这些.o就是gcc提供的启动代码。

(2)gcc -v

gcc -v a.c

在编译的详细信息里面,有很多的事先就被编译好的.o文件,这些.o文件就是用来生成启动代码的

1.3 启动代码做了些什么 

1.3.1 启动代码使用什么语言编写的

基本都是汇编写的。

1.3.2 启动代码大致做了些什么呢? 

大致上有两件重要的事情:

· 对c程序的内存空间进行布局,得到c程序运行所需要的内存空间结构。

· 留下相应库接口

(1)对c内存空间进行布局

c等高级语言程序在运行时,函数调用需要“栈”,启动代码就需要在c内存空间上建立“栈”,

说白了就是从从c内存空间中划出一段空间,然后以“栈”的形式来进行管理。

 

· 思考:为什么启动代码,基本都是使用汇编来编写?

  •  在程序的内存空间结构还没有布局起来之前,高级语言程序还无法运行,此时只能使用汇编
  •  当利用汇编编写的启动代码将高级语言的内存空间结构建立起来后,自然就可以运行c/c++等高级语言的程序了。

(2)为库的调用预留接口

如果程序使用的是动态库的话,编译时,动态库代码并不会被直接编译到程序中,只会留下相应的接口

程序运行起来后,才会去对接库代码,为了能够对接动态库,启动代码会留下动态库的对接接口。

1.4 程序是如何运行起来的

1.4.1 裸机的情况 

(1)内存和硬盘一体式

1)典型的比如51单片机,51没有单独的内存和单独硬盘,使用的是内存和硬盘功能二合一的norflash。

    (a)为什么能身兼内存的功能?

            因为norflash的访问速度很快,因此cpu能够直接从norflash上读取指令并执行,此时norflash就是一个内存。

    (b)为什么身兼硬盘的功能?

            因为norflash能够永久保存数据,设备关电后,数据依然存在

2)程序运行的过程

            

            

(2)内存和硬盘分开式 

比如arm芯片

1)为什么传统的计算机,硬盘和内存都是分开的

               

2)内存和硬盘分开的这种情况,如果直接以裸机方式使用的话,程序是如何运行起来的

       两种:
                      第一种:直接将下载到内存中,然后运行。
                      第二种:先下载硬盘永久保存,开机时自动从硬盘中将代码拷贝到内存上,然后运行。

      (a)将程序直接下载到内存

                

                这种方式最大的缺点就是掉电就没了,所以这种方式只适合于平时的测试 。

       (b)将程序下载到硬盘

               

1.4.1 有OS的情况  

 上OS的计算机,基本都是内存和硬盘分开式的情况。

  OS是怎么运行启动起来的呢?

  

(1)有OS时,可执行程序都是直接放在了硬盘上

(2)有OS时,程序如何运行起来

       1)有OS支持时,如何启动程序呢

           (a)裸机时,是怎么启动程序的呢?

                    

          (b)有OS支持时,启动程序方式有三种

                     · 双击快捷图标运行
                     · 在命令行运行    
                     · 设置为开机自启动    
      2)OS是怎么实现拷贝的

                对于我们自己写的裸机程序来说,我们需要自己写拷贝代码

                对于OS这个裸机程序来说,由启动程序来负责代码的拷贝。

                

2. 进程(程序)的终止方式

2.1 正常终止

进程主动调用终止函数/返回关键字所实现的结束,就是正常终止。

        · main调用return关键字结束
        · 程序任何位置调用exit函数结束
        · 程序任何位置调用_exit函数结束

1)main函数调用return关键字

return关键字的作用是返回上一级函数,如果main函数的子函数调用return的话,返回的上一级是main函数。
如果main函数调用return的话,main函数所返回的上一级是启动代码。

(a)显式调用

· 返回值的意义

    

疑问:如果return时我不写返回值会怎样呢?

   

(b)隐式调用

就是不明写出return,当main函数中的最后一句代码执行完毕后,会默认的调用return返回

不过隐式return时,默认返回0。

2)在程序的任意位置调用exit函数 

其实,main函数调用return返回到启动代码后,启动代码也是调用exit函数来实现正常终止的。

#include <stdlib.h>

void exit(int status);

这个参数就是返回值(进程终止状态)。

main函数调用return将返回值返回给启动代码后,启动代码又会调用exit(返回值),将返回值返回。

3)在程序的任意位置调用_exit函数 

_exit是一个系统函数(系统API),而exit是c库函数,exit就是调用_exit来实现的。

4)return、exit和_exit的返回值问题

 return、exit和_exit的返回值,也被称为进程终止状态。

(a)裸机时:

(b)有OS时:

 return、exit、_exit,使用哪种来返回都行。

 

2.2、异常终止

进程不是因为return、exit和_exit函数而终止的,而是被强行发送了一个信号给无条件终止了,这就是异常终止。

2.3 atexit函数

函数原型

#include <stdlib.h>

int atexit(void (*function)(void));

   

   

登记“进程终止处理函数”有什么意义?

   

2.4 有OS时,进程从启动 到 正常终止的全过程

2.4.1 图示

 

2.4.2 为什么调用exit、return正常终止时,会刷新标准io的缓存

 有关标准IO的库缓存的缓冲有三种,无缓冲、行缓冲、全缓冲

(1)回顾行缓冲

标准输出(printf)的库缓存就是行缓冲的,在缓存中积压数据,直到以下情况时,才会刷新输出,否则一直挤压】

(2)为什么调用exit正常终止时,会刷新标准io的缓存呢?

  •  因为exit会调用fclose关闭所有的标准io,关闭时会自动调用fflush来刷新数据。
  •  这里要注意:如果进程时异常终止的话,是不会刷新缓存区的,因为异常退出时,跟exit函数半毛钱关系都没有。

2.5 命令行参数

第一个参数永远都是程序名

将命令行参数传递给main函数形参的过程

3. 环境变量表    

3.1 windows的环境变量    

3.1.1 为什么在命令行执行我自己的程序,需要指明路径

在windows下,如果你不加路径的话,会道默认到当前路径下找程序,没有的话就找不到你的程序。

3.1.2 能不能不加路径,我随便在什么目录下都可执行我的程序呢?

当然可以,只要把程序所在路径,加入windows的path环境变量即可。

3.1.3 为什么设置了path环境变量后,可以不加路径就能执行程序

(1)path这个环境变量的作用

专门记录各种可执行程序所在路径。

(2)path记录后

(3)path的意义

一般来说,我们自己的安装的程序,都没有设置环境变量

3.1.4 再来看看windows的环境变量

3.2 环境变量表

(1)什么是环境变量表

用于存“放环境变量”的表,就是环境变量表。

什么是环境变量呢?

答:其实就是进程在运行时,会用到的一些字符串信息,环境表就好比是工具箱,

       里面放了各种进程运行时需要用到的“工具”,比如各种的路径。
 

(2)环境变量文件

(3)每个进程的环境变量表

每一个进程都在自己的内存空间(堆空间)保存了一份自己的环境变量表。

 

每个进程空间中的环境变量表又是怎么来的?

                     显然从环境变量文件中得来的。

(4)如果某环境变量的数据有很多条,在环境变量表中,多条数据之间怎么区分

在windows这边使用;分隔,Linux这边则使用:分隔。

(5)为什么只有重新打开“命令行窗口”后, 新设置的“环境变量”才生效?

3.3 Linux的环境变量

 修改Linux的环境变量表

(1)永久修改

1)图形方式操作

2)直接修改“环境变量文件”

(2)临时修改

· 什么是临时修改?

就是只修改当前进程自己的“环境变量表”,其它不相关进程“环境变量表”及“环境变量文件”中数据,不会发生任何变化,

· 如何实现临时修改

1)如何使用命令来修改“命令行窗口进程”的环境变量表

(a)查看所有环境变量

(b)显示单个的环境变量

(c)添加一个新的环境变量

(d)修改已有环境变量

(d)删除

2)通过API修改环境变量

对于我们自己所写的程序来说,我们可以调用API来修改自己所写程序的“环境变量表”。

(a)获取环境表中的所有环境变量

1  environ全局变量:   char **environ;

    - environ与main函数的argv一样,指向的都是一个字符串指针数组。

         argv:与命令行参数有关
         environ:与环境变量表有关

 
2 main函数的第三个参数: char **environ

(b)调用API:实现环境变量的添加、修改等

· putenv、setenv:添加和修改环境变量

#include <stdlib.h>

int putenv(char *string);
int setenv(const char *name, const char *value, int overwrite);

  

 

· unsetenv:删除环境变量

#include <stdlib.h>

int unsetenv(const char *name);

 

·getenv:获取环境变量 

(c)疑问:我自己所写程序的环境表是怎么来的

我命令行窗口执行./a.out,那么a.out进程就属于“命令行窗口进程”的子进程,子进程的环境表是从父进程复制得到的

 

当有OS支持时,基本所有的进程都是由父进程“生”出来的: 

疑问1:最原始的进程从哪来的 

     答:OS启动完毕后演变得到的。

4. c程序内存空间布局

1)什么是c程序的内存空间

c程序运行时,是运行在内存上的,也就是说需要在内存上开辟出一块空间给c程序,然后将C代码从硬盘拷贝到内存空间上运行,至于说是不是将代码全部会被拷贝到内存上,这就不一定的了

2)c程序的内存空间结构

(a)有结构的要求吗?

有,这段空间必须布局为c程序运行所需的空间结构,c程序才能运行

如果空间没有布局好,进程将无法运行,因此程序的内存空间布局是非常重要的进程环境。

(b)c的内存结构是谁来构建的

是由启动代码来搭建的,比如启动代码会把c内存空间的某一部分空间构建为“栈”,或者说以“栈”的方式来管理这片空间。

(c)不光是C程序

所有高级语言的程序在运行时,都涉及内存空间的结构布局,不过它们的结构都是相似的

5. 库    

写程序时,绝对不可能从零开始写代码,都是要依赖别人所写的代码的,比如别人写的库,所以库也是程序非常重要的

进程环境,没有库的支持,我们的程序根本做不了什么复杂的事情。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章