内核执行C程序时,使用一个exec函数,在调用main前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址:--这是由连接编辑器设置的,而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和环境变量值。
8忠方式使进程终止
五种正常终止
1,从main返回
2,调用exit
3,调用_exit或_Exit
4,最后一个线程从其启动例程返回。
5,最后一个线程调用pthread_exit。
异常终止有3种方式
6,调用abort
7,接到一个信号并终止
8,最后一个线程对取消请求做出响应。
-----------------------------------------
exit函数
#include<stdlib.h>
void exit(int status)
void _Exit(int status)
#include <unistd.h>
void _exit(int status)
exit函数总是执行一个标准I/O库的清理关闭操作:为所有打开流调用fclose函数。这会造成所有缓冲的输出数据都被冲洗。
三个exit函数都带一个整型参数,称为终止状态。大多数unix shell都提供检查进程终止状态的方法。如果a若调用这些函数时不带终止状态,或b main函数执行了一个无返回值的return语句,或c main没有声明返回类型为整数,则该进程的终止状态是未定义的。但是,若main的返回类型是整型,并且main执行到最后一个语句时返回,那么该进程的终止状态是0.
exit(0)等价于return (0)
-----------------------------------------
atexit函数
一个进程可以登记多达32个函数,这些函数将由exit自动调用。称这些函数为终止处理程序,并调用atexit函数来登记这些函数。调用顺序与登记顺序相反。统一函数如若登记多次,也会被调用多次。
-------------------------------------------
环境表
每个程序都会接收到一张环境表。环境表释义个字符指针数组,每个指针包含一个以null结束的C字符串的地址。全局变量environ则包含了该指针数组的地址
extern char **environ
environ:环境指针->环境表
项1-> HOME=/home/sar/0
项2-> PATH=:/bin:/usr/bin/0
NULL
----------------------------------------
C程序的存储空间分布
1,正文段,由CPU执行的机器指令部分。
2,初始化数据段,数据段,包含了程序中需明确地赋初值的变量。C程序中出现在任何函数之外的声明:
int maxcount =99;
3,非初始化数据段,称为bss段,在程序开始执行之前,内核将此段中的数据初始化为0或空指针。由符号开始的块.出现在任何函数外的C声明
long sum[1000]
4,栈,自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次调用函数时,其返回地址以及调用者的环境信息都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。
5,堆。
对x86处理器上的linux,正文段从0x08048000开始,栈底则在0xC0000000之下开始,栈从高地址向低地址方向增长。
高地址 ----------------------------
|命令行参数和环境变量 |
----------------------------
| 栈底 |
| |
| |
| |
| 堆 |
----------------------------
| 未初始化的数据 |由exec初始化为0
----------------------------
| 初始化的数据 |由exec从程序文件中读入
----------------------------
低地址 | 正文 |由exec从程序文件中读入
----------------------------
未初始化的数据段的内容并不存放在磁盘的程序文件中。其原因是,内核在程序开始运行前将它们都设置为0。需要存放在程序文件中的段只有正文段和初始化数据段。
size命令可以用来查看
---------------------------------------------
共享库
使得可执行文件中不在需要包含共用的库例程,而只需要在所有进程都可引用的存储区中维护这种库例程的一个副本。程序第一次执行或者第一次调用某个库函数时,用动态链接方法将程序与共享库函数相链接。减少了每个可执行文件的长度,增加了一些运行时间开销。这种事间开销发生在该程序第一次被执行时,或者每个共享库函数第一次被调用时。
----------------------------------------------
存储器分配
void malloc(size_t size)
void calloc(size_t nobj, size_t size)
void realloc(void *ptr, size_t newsize)
void free(void * ptr)
大多数实现所分配的存储空间比所要气度额要稍大一些,额外的空间用来记录管理信息--分配块的长度,指向下一个分配块的指针等等。这就意味着如果超过一个已分配区的尾端进行写操作,则会重写后一个块的管理记录。
在动态分配的缓冲区前或后进行写操作,破坏的可能不仅仅是该区的管理记录信息。在动态分配的缓冲区前后的存储区很可能用于其他动态分配的对象。
--------------------------------------------------
环境变量
环境字符串形式
name=value
unix内核并不查看这些字符串,他们的解释完全取决于各个应用程序。
#include<stdlib.h>
char * getenv(const char *name)返回值:指向与name关联的value的指针,若未找到则返回null。
应当使用getenv而不是直接访问environ
#include<stdlib.h>
int putenv(char*str)
取形式为name=value的字符串,将其放到环境表中,如果name已经存在,则先删除其原来的定义。
实现将传送给它的字符串地址作为参数直接放入环境表中。
int setenv(const char *name, const char*value, int rewrite)
将name设置为value。如果环境中name已经存在,那么(a)若rewrite非0,则首先删除其现有的定义;(b)若rewrite为0,则不删除其现有定义。
int unsetenv(const char *name)
删除name的定义。
环境表和环境字符串通常存放在进程存储空间的顶部(栈之上)。删除一个字符串很简单,只要先在环境表中找到该指针,然后将所有后续指针都向环境表首部顺次移动一个位置。环境表和环境字符串通常占用的是进程地址空间的顶部,所以它不可能再向高地址方向(向上)扩展,同时也不能移动在它之下的各栈帧,所以它也不能向低地址方向(向下)扩展。
1)如果修改一个现有name:
a)如果短,直接修改
b)如果长,必须调用malloc分配空间,然后将新字符串复制到该空间中,调整环境表指针。
2)增加一个name
a)第一次增加一个新name,调用malloc为新的指针表分配空间,将原来的环境表复制到新分配区。并将指向新name=value字符串的指针存放在该指针表的表尾,然后又将一个空指针存放在其后。最后使environ指向新指针表。
b)用realloc分配空间,类似于a)
-------------------------------------------
setjmp和longjmp
可以跨越函数。对于处理发生在深层嵌套函数调用中的出错情况是非常有用的。
不是由普通C语言goto语句在一个函数内实施的跳转,而是在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中。
#include<setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
在希望返回到的位置调用setjmp,参数env是一个特殊类型jmp_buf。这一数据类型是某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息。因为需要在另一个函数中引用env变量,所以规范处理方式是将env定义为全局。以两个参数调用longjmp函数。第一个就是在调用setjmp时所用的env,第二个参数是具有非0值的val,它将成为从Setjmp处返回的值。对于一个setjmp可以有多个longjmp。通过返回值,可以判断返回发生在那个函数中。
自动,寄存器和易失变量。
当longjmp返回到main函数时,这些变量的值是否能恢复到以前调用setjmp时的值?这个要看情况。大多数实现并不会滚这些自动变量和寄存器变量的值,而所有标准则说它们的值是不确定的。如果有一个自动变量,而又不想使其值回滚,则可定义其为具有volatile属性。声明为全局或静态变量的值在执行longjmp时保持不变。
参考volatile
自动变量的潜在问题
FILE * open_data(void)
{
FILE *fp;
char databuf[BUFSIZ];
if((fp = fopen(DATAFILE, "r")) == NULL)
return(NULL);
if(setvbuf(fp, databuf, _IOLBF, BUFSIZ) != 0)
return(NULL);
return (fp);
}
当open_data返回时,它在栈上所使用的空间将由下一个被调用函数的栈帧使用,但是,标准I/O库函数仍将使用其流缓冲区的存储空间。这就产生了冲突。应在全局存储空间静态地或者动态地为数组databuf分配空间。
------------------------------------------------------
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
进程的资源限制通常实在系统初始化时由进程0建立的,然后由每个后续进程继承。
对这两个函数的每一次调用都会指定一个资源以及一个指向下列结构的指针。
struct rlimit{
rlim_t rlim_cur; /*soft limit:current limit */
rlim_t rlim_max; /*hard limit:maximum value for rlim_cur*/
};
更改的三条规则:
1)任何一个进程都可将一个软限制值更改为小于或等于其硬限制值
2)任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低对普通用户而言是不可逆的。
3)只有超级用户进程可以提高硬限制值。
常量RLIM_INFINITY指定了一个无限量的限制。
资源限制影响到调用进程并由其子进程继承。这就意味着为了影响一个用户的所有后续进程,需将资源限制的设置构造在shell之中。