1、main函数数组
C程序老是从main函数开始执行。main函数的原型是:编辑器
int main(int argv, char *argv[]);
当内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址---这是由链接编辑器设置的,而链接编辑器则由C编译器调用。函数
2、进程终止布局
一共有8种方式进程终止,其中五种为正常终止,他们是:一、从main返回;二、调用exit;三、调用_exit或_Exit;四、最后一个线程从其启动例程返回;五、从最后一个线程调用pthread_exit。异常终止有三种:一、调用abort;二、接到一个信号;三、最后一个线程对取消请求作出响应。优化
一、退出函数spa
3个函数用于正常终止一个程序,_exit和_Exit当即进入内核,exit则限制性一些清理处理,而后返回内核。线程
#include <stdlib.h> void exit(int status); void _Exit(int status); #include <unistd.h>] void _exit(int status);
因为历史缘由,exit函数老是执行一个标准I/O库的清理关闭操做:对于全部打开流调用fclose函数,这形成输出缓冲中的全部数据都被冲洗。3个退出函数都带一个整型参数,称之为终止状态。若是main没有声明返回类型为整型,则该进程的终止状态是未定义的。可是若main的返回类型是整型,而且main执行到最后一条语句时返回,那么该进程的终止状态是0。unix
2.函数atexit指针
按照ISO C的规定,一个进程能够登记多至32个函数,这些函数将有exit自动调用。咱们称这些函数为终止 处理程序(exit handler),并调用atexit函数来登记这些函数。code
#include <stdlb.h> int atexit(void (*func)(void));
atexit的参数是一个函数地址,当调用次函数时无需向他传递任何参数,也不指望它返回一个值。exit调用这些函数的顺序与他们登记时候的顺序相反。同一函数如登记屡次,也会被调用屡次。exit首先调用个终止处理程序,然会关闭全部打开流。注意,内核是程序执行的惟一方法是调用一个exec函数,进程自愿终止的惟一方法是显式或隐式调用_exit或_Exit。进程也可非自愿地由一个信号使其终止。
7-2 一个C程序如何启动和终止的
3、环境表
每一个程序都接收到一张环境表。与参数表同样,环境表也是一个字符指针数组,其中每一个指针包含一个以null结束的C字符串的地址。全局变量environ则包含了该指针数组的地址: extern char **environ。
4、C程序的存储空间布局
历史沿袭至今,C程序一直由下列及部分组成:
一、正文段:CPU执行的极其指令部分。一般正文段是可共享的,因此即便是频繁执行的程序在存储器中也只需有一个副本,另外正文段经常是只读的,以防止程序因为意外而修改其指令。
二、初始化数据段:程序中需明确地赋初值的变量,一般称之为数据段。
三、未初始化数据段:一般将此段称为bbs段(block started by symbol),在程序开始执行以前,内核将此段中的数据初始化为0或空指针。未初始化的全局变量就放在这个段。
四、栈:自动变以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址以及调用者的环境信息都存放在栈中。而后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。经过以这种方式使用栈,C递归函数能够工做。递归函数每次调用时自身时,就用一个新的帧栈,所以一次函数嗲用实力中的变量集不会影响另外一次函数调用实例中的变量。
五、堆、一般在堆中进行动态存储分配,因为历史缘由,堆位于未初始化数据段和栈之间
7-6 典型的存储空间安排
未初始化数据段的内容并不存放在磁盘程序文件中。其缘由是内核在程序开始运行前将他们都设置为0,须要放在磁盘程序文件中的段只有正文段和初始化数据段。
5、共享库
共享库是的可执行文件中再也不须要包含公用的库函数,而只须要在全部进程均可引用的存储区中保存这种库例程的一个副本,程序第一次执行或第一次调用某个库函数时,用动态链接方法与共享库函数相链接,者减小了每一个可执行文件的长度,但增长了一些运行时间。共享库还有一个优势,就是能够用新版本库函数替换老版本库函数,而无需对程序进行从新编译。
6、存储空间分配
ISOC说明了3个用于储存空间动态分配的函数:
#include <stdlib.h> void *malloc(size_t size); void *calloc(size_t nobj, size_t size); void *realloc(void *ptr, size_t newsize); void free(void *tpr);
malloc、分配指定字节数的存储区。此存储区中的初始值不肯定;calloc,为指定数量长度的对象分配存储空间。该空间中的每一位都初始化为0;realloc,增长或减小之前分配区的长度,当增长长度时,可能需将之前分配区的内容移到另外一个足够大的区域,一边在尾端提供增长的存储去,而新增区域内的初始值不肯定。这3个函数所返回的指针必定是适当对齐的,使其可用于任何数据对象。
这些分配例程一般用sbrk系统调用实现。该系统调用扩充进程的堆。大多数实现分配的存储空间比所要求的要稍大一些,额外的空间用来记录惯例信息----分配快的长度、指向下一个分配快的指针等。这就意味着,若是超过一个已分配的尾端或者在已分配区起起始位置以前进行写操做,则会该写另外一块的管理信息。
7、环境变量
环境字符串的形式是: name=value,unix内核并不查看这些字符串,他们的解释彻底取决于各个应用程序。ISO C定义了一个函数getenv,能够用其取环境变量值,可是该标准又称环境的内容是由实现定义的。ISO C没有定义任何环境变量
#include <stdlib.h> char *getchar(const char *name);
putenv取形式为name=value的字符串,将其防盗环境表中,若是name已经存在,则先删除其原来的定义;setenv将name设置为value。若是name已经存在,若是rewrite非0,则首先删除其现有的定义,不然不删除其现有定义。unsetenv删除name的定义。即便不存在这种定义也不算出错。
#include <stdlib.h> int putenv(char *str); int setenv(const char *name, const char *value, int rewrite); int unsetenve(const char *name);
环境表和环境字符串一般存放在进程存储空间的顶部(栈之上)。删除一个字符串很简单---只要先在环境表中找到该指针,而后将全部后续指针都向环境表顺不顺序移动一个位置就能够了。若是修改一个现有的name:若是新value的长度少于或等于现有value的长度,则只要将新字符串复制到元字符串所用的空间中便可;若是新value的长度大于元长度,则必须调用malloc为新字符串分配空间,而后将新字符串复制到空间中,接着使黄竞标中针对name的指针指向新分配区。
8、函数setjmp和longjmp
在C中,goto语句是不能跨越函数的,而执行这种类型跳转功能的函数是setjmp和longjmp。这两个函数对于处理发生很深层嵌套函数调用中的出错状况是很是有用的。
#include "apue.h" #define TOK_ADD 5 void do_line(char *); void cmd_add(void); int get_token(void); int main(void) { char line[MAXLINE]; while(fgets(line, MAXLINE, stdin) != NULL) { do_line(line); } exit(0); } char *tok_ptr; void do_line(char * ptr) { int cmd; tok_ptr = ptr; while((cmd = get_token()) > 0) { switch(cmd) { case TOK_ADD: cmdd_add(); break; } } } void cmd_add(void) { int token; token = get_token(); } int get_token(void) { }
7-9 进行命令处理程序的典型骨架部分
自动变量的存储单元在每一个函数的帧栈中。数组line在main的帧栈中,整型cmd在do_line的帧栈中,整型token在cmd_add的帧栈中。多层次嵌套调用返回错误可使用非局部goto---setjmp和longjmp函数。非局部指的是,这不是由普通的C语言goto语句在一个函数内实施的跳转,二是在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中。
#include <setjmp.h> int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val);
#include "apue.h" #include <setjmp.h> #define TOK_ADD 5 jmp_buf jmpbuffer; int main(void) { char line[MAXLINE]; if(setjmp(jmpbuffer) != 0) { printf("error"); } while(fgets(line, MAXLINE, stdin) != NULL) { do_line(line); } exit(0); } ... void cmd_add(void) { int token; token = get_token(); if(token < 0) { longjmp(jmpbuffer, 1); } }
7-11 setjmp和longjmp实例
#include "apue.h" #incldue <setjmp.h> static void f1(int, int, int, int); static void f2(void); static jmp_buf jmpbuffer; static int globval; int main(void) { int autoval; register int regival; volatile int volaval; static int statval; globval = 1; autoval = 2; regival = 3; volaval = 4; statval = 5; if(setjmp(jmpbuffer) != 0) { printf("after longjmp:\n"); printf("globval = %d, autoval = %d, regival = %d, volaval = %d, statval = %d\n", globval, autoval, regival, volaval, statval); exit(0); } globval = 95; autoval = 96; regival = 97; volaval = 98; statval = 99; f1(globval, autoval, regival, volaval, statval); exit(0); } static void f1(int i, int j, int k, int l) { printf("globval = %d, autoval = %d, regival = %d, volaval = %d, statval = %d\n", globval, i, j, k, l); f2(); } static void f2(void) { longjmp(jmpbuffer, 1); }
7-13 longjmp对各种变量的影响
全局变量、静态变量和易失变量不受优化的影响,在longjmp以后,他们的值是最近所呈现的值。经过这一实例咱们能够理解到,若是要编写一个使用非局部跳转的可移植程序,则必须使用volatile属性。
9、函数getrlimit和setrlimit
每一个进程都有一组资源限制,其中一些能够用getrlimit和setrlimit函数查询和更改。
#include <sys/resource.h> int getrlimit(int resource, struct rlimit *rlptr); int setrlimit(int resource, const struc rlimit *rlptr);
在更改资源限制时,遵循如下条件:一、任何一个进程可将一个软限制值更改成小于或等于其硬件限制值;二、任何一个进程均可下降其硬限制值,但必须大于或等于其软限制值;三、只有超级用户进程能够提升硬限制值;四、资源限制影响到调用进程并由子进程继承。