鸿 网 互 联 www.68idc.cn

当前位置 : 服务器租用 > 编程语言开发 > erlang > >

linux进程(1)--进程运行的环境

来源:互联网 作者:佚名 时间:2016-07-17 21:13
linux进程(1)–进程运行的环境 标签(空格分隔): linux 以下内容来自《UNIX环境高级编程》读书笔记 前引 首先想想下面几个问题能不能解答: 当程序被执行的时候,main函数时如何被调用的? 程序在内存的存储空间布局是怎样的? 命令行参数时如何传递给新

linux进程(1)–进程运行的环境

标签(空格分隔): linux


1232

以下内容来自《UNIX环境高级编程》读书笔记

前引

首先想想下面几个问题能不能解答:

  • 当程序被执行的时候,main函数时如何被调用的?
  • 程序在内存的存储空间布局是怎样的?
  • 命令行参数时如何传递给新程序的?进程如何读取环境变量?
  • 进程堆空间的使用
  • 进程的终止方式

进程是程序执行的基本,进程即为程序执行的活动体。下面是进程在系统上运行的一些环境。

一、main函数

int main(int argc, char *argv[]);

当执行c程序的时候,在调用main函数之前会调用一个特殊的启动例程。可执行程序文件将次启动例程(Start Routin)指定为程序的起始地址(这是有链接的过程设置的)。启动例程会去内核中取命令行参数和环境变量值,然后对调用main函数之前进行一些环境的初始化。

对启动例程感兴趣的可以参考下面的链接看下:
how does linux execute my main()?
main函数和启动例程

二、进程的终止

书上说的是有8种情况下进程终止:
5中正常终止:

  • 从main返回;
  • 调用exit;
  • 调用_exit或者_Exit;
  • 最后一个线程从其启动例程返回;
  • 从最后一个线程调用pthread_exit;

3种异常终止:

  • 调用abort
  • 调用一个信号
  • 最后一个线程对取消请求做出响应

1.退出函数

#include <stdlib.h>
void exit(int status);
void _Exit(int status);

#include <unistd.h>
void _exit(int status);

前面两种属于ISO C的,后面一种属于POSIX
_Exit = _exit 作用相同
exit与_exit的差别在于,_exit会直接返回内核,而exit会先进行一些清理操作。

C代码的启动例程可以是这样的:

exit(main(int argc, char* argv[]))

1.1 exit

书上说因为什么历史原因,导致exit函数总是执行一个标准IO库的清理操作:即对所有打开的流调用fclose函数,这就造成输出缓冲的数据都被冲洗(刷新:写到文件中)

三个函数都有一个status参数值,这是进程终止的状态值。

还记得原来刚学习的时候有main函数如下写法:

void main(void)

就相当于最后结束的时候只是return,没有返回值,给exit的status就是空的。

return(0) = exit(0)

有一点指明的是,当main函数没有返回值,那么他的返回值是随机的,未定义的。

main函数声明为返回int的,这是POSIX和ISO定义。

1.2 atexit

进程登记函数,ISO的规定一个进程可以登记最多32个函数,这些函数有exit自动调用,这些函数为终止处理函数.

#include <stdlib.h>
int atexit(void (*func)(void));
    //若成功,返回0;若出错,返回非0

参数是一个函数地址。
exit调用这些函数的顺序与登记顺序相反(估计登记的时候是放在栈中的。)同一个函数若登记多次,也会调用多次。

前面所说的这些都可以用下面这张图来解释清楚:c程序启动和终止
c程序启动和终止
注意,内核使程序执行的唯一方法是调用一个exec函数来调用程序。进程自愿终止的唯一方法是显式的或者隐式的(通过调用exit)调用_exit或者_Exit。进程也可以非自愿的有一个信号使其终结。

三、进程的环境

3.1 命令行参数

调用exec执行一个程序的进程可以将命令行参数传递给该新程序。
参数存储在一张参数表中,类似于环境表,如下。

3.2 环境表与环境变量

每个程序接受一张环境表,环境表是一个字符指针数组,只是存储环境变量的字符串的首指针。全局变量environ包含该指针数组的地址:

extren char **environ;

环境表的结构如下:
environ

环境变量在linux等系统属于必须知道的东西。
环境变量字符串形式是key-value形式的。linux的环境变量设置和查看的底层函数:

#include <stdlib.h>
char *getenv(const char *name)
//成功,返回name关联的value指针;没有找到的,返回NULL

int putenv(char *str)
//成功返回0  失败返回非0

int setenv(const char *name, const char *value, int rewrite)
int unsetenv(const char *name)

简单的说下这几个函数:

  • getenv 读取某个环境变量的值,前提是存在这个环境变量
  • putenv 取形式为name=value的字符串,将其放在环境表中,如果name已经存在,就先删除原来的value
  • setenv 将name的值设置为value。最后一个参数rewrite是为了控制当name存在时,rewrite!=0时,先删除其现有的定义。rewrite==0 时,则不删其现有的定义 (name不设置为新的值,也不报错。)
  • unsetenv 删除name的定义。即使不存在这种定义也不为错。

四、C程序的存储空间布局

存储空间布局
这是一种经典的存储模型。

  • 正文段:存储CPU执行指令的部分,正文段是共享的,这样可以使得那些频繁执行的程序只需要再内存中有一个副本。一般正文段是只读的。
  • 初始化数据段:全局变量和静态变量,并且初始化过的一般存储在这里。
  • 未初始化数据段: bss字段,意思为“由符号开始的块”。一般在目标文件(xxx.o文件中他是不占空间的),在程序执行之前,内核将此段中的数据初始化为0或者NULL空指针。
  • 栈:局部变量,自动变量。每次函数调用所需保存的现场数据(程序执行的位置和栈帧返回位置)存放在栈中。一个函数调用一个栈帧。
  • 堆:动态分配的数据块。

图中显示的是这些段的一种经典的安排。
栈顶和堆顶之间的未用的虚拟空间很大。

a.out中还包含了若干其他类型的段,如包含符号表的段,包含调试信息的段,以及动态共享库链接表的段等(《程序员的自我修养》中讲的很清楚)。但是这些部分并不会装载到进程执行的程序映像中的。

图中看出:

  • 命令行参数和环境变量存储在栈顶上面的空间中。
  • 未初始化的数据bss段不存储到磁盘中,运行的时候内核赋值。
  • 程序文件中存储的是正文执行代码和初始化数据。

五、共享库

共享库使得可执行文件中不再需要包含公用的库函数,而只需在所有进程都可引用的存储区中保存这种库例程的一个副本。这样可以大大的减少每个执行文件的长度。
共享库的另外一个优点是:新版本的库替代老版本无需使用该库的程序重新连接编辑(只要接口不变的话)。

六、动态存储空间的分配

存储空间动态分配函数:

#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
//函数返回值:成功返回非空指针,出错返回NULL

void free(void *ptr)

这些操作也是最容易出错的,并且出错之后很难查找那种。

  • 越界操作:越界操作意味着有可能会去改变下一个堆存储的数据或者改变每个堆存储前面的管理信息,这些错误都是致命的错误,并且很难查找的错误。
  • 释放一个已经释放的块。
  • 内存泄露。隐形的内存泄露很危险。

alloca函数时在栈中申请存储空间的函数,有些情况下很有用的。
alloca

以上基本是进程运行环境。存储,变量,代码,地址。

网友评论
<