进程和程序
首先要区分一下进程和程序
- 进程:进程是内核定义的抽象的实体,包含用以执行程序的各种资源
- 程序:程序是文件,里面存放的信息告诉内核如何创建一个进程
也就是说,程序其实和进程不是一个概念。程序是一个用以描述进程的文件,包括
- 二进制文件标识:描述进程的元信息,现在的Linux系统一般都是
ELF格式 - 机器语言指令:也就是代码,将要存放在汇编语言中的
.text段中 - 程序入口地址:也就是对应汇编中的
_start标号地址 - 符号表和重定位表:描述函数和变量的位置和名称,将要存放于
.data .bbs段中 - 共享库和动态链接信息:记录了链接库的信息
- 其他信息
进程号和父进程号
使用
| |
获得当前进程号,使用
| |
获得当前进程的父进程号。
注意这两个函数都一定会执行成功,总是返回0
所有的进程都是由init进程(进程号为1)的父进程创建的。
进程的内存布局
当内核打开一个程序的时候,会自动创建一个进程,进程包含的内容其实在Unix汇编中以及提到过了:
- 文本段
.text - 初始化数据段
.data - 未初始化数据段
.bbs - 栈
- 堆:运行时动态为变量分配的内存空间
虚拟内存
虚拟内存会将程序使用的内存切割成小型的,固定大小的“页”。相应的会将RAM以页为单位分割。同一时刻只有部分内存放在页中,其他不用的内存放在交换区中(也就是磁盘上).这样就会导致程序使用的内存甚至超出了RAM的大小(多出的部分放在交换区中)。
栈和栈帧
函数调用和返回会在栈上新增加一个栈帧(里面存放着参数等信息),当函数调用完毕之后会将栈帧从栈上移除。

命令行参数小技巧
我们都知道C语言里面有argc, **argv两个命令行参数,而且argv[0]总是程序的名称,这里有一个小技巧(也是gzip,gunzip,zcat使用的):通过文件名称来让同一个程序执行不同的操作。
因为连接档运行之后argv[0]的名称也是连接档的,所以我们可以写类似下面的程序:
| |
没错可以根据不同的名称执行不同的功能,但是最后其实都是一个程序哦,只不过其他的不同名的程序是连接档罢了。
环境列表
环境列表其实就是存储环境变量的列表啦。只不过里面的环境变量都是以变量名=变量值来定义的。
子进程会将父进程的环境列表拷贝一份,这也就是为什么终端需要一个.bashrc .profile这种文件来存放环境变量的原因了,通过终端打开的程序都会获得终端的环境列表的一份拷贝,这也是进程之间传递信息的一种方式。
从程序中访问环境列表
通过environ变量
C中提供了全局变量environ存储着本进程的环境列表,他是一个char**类型的,最后以NULL结尾的列表。
| |
结果:
| |
这里只输出了前10个环境变量。
或者通过增加main函数的参数来直接获得!
| |

通过函数获得或修改
获得
可以通过char *getenv(const char* name)函数获得环境变量,参数是环境变量的名称,返回对应的值。
注意:
- 程序不应该修改此函数返回的值
- 允许使用静态分配的缓冲区返回结果
修改
通过
| |
来修改环境变量
putenv()通过写入name=value形式来创建变量。如果变量存在会覆盖。
需要注意的是,函数不会拷贝参数,也就是说string参数后续改变会影响到环境变量,所以不应该是使用在栈上分配的字符串数组作为参数(一旦被改变会引起逻辑错误)。
setenv()不需要在env,value参数之间加上=,通过overwrite参数指定如果变量存在是否覆盖(0不覆盖,非0覆盖)
清除
使用
| |
函数即可,其实内部就是environ=NULL语句。
非局部跳转
需要引用setjmp.h
非局部跳转就是从一个函数跳到另一个函数。其实在函数内部跳转已经有goto语句了。同goto语句一样,非局部跳转语句不提倡被使用(其实我自从离开了C语言教程之后还真没见过代码里面有goto的。。。)
执行非局部跳转的话,首先你得有一个变量来保存跳转之前的状态,方便跳转之后返回原点,这个变量的类型是jmp_buf。
然后需要跳转函数,int setjmp(jmp_buf env)函数将env传入进去保存当前的程序信息,以便于跳转。int longjmmp(jmp_buf env, int val)函数会依据env参数保存的信息跳转回去,并且让对应的setjmp函数返回val值。
这里尤其需要注意一下setjmp函数的返回值。当你使用setjmp函数之后如果返回0代表成功了,然后此时的程序信息保存在env变量中。接下来如果你执行了longjmp(env, value)函数,那么会跳转回setjmp函数处,兵器setjmp函数会再次返回,返回值为longjmp指定的value。
也就是说,value参数其实是为了方便程序员从setjmp函数处判断是哪个函数跳转回来的。
例子:
| |
这里首先调用了setjmp然后对返回值判断,第一次setjmp返回0代表成功了,那么我们就进入case 0调用f1()函数。在f1()函数中执行了longjmp()函数跳转回setjmp处,这个时候setjmp会返回1,于是输出jumped to f1()。由于f2()的调用在longjmp下面,所以函数跳转回去并没有执行f2()
| |