进程环境:
main函数:
C程序总是从main函数开始执行。main函数的原型是:
int main(int argc,char *argv[]);
其中,argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组。
当内核执行c程序时(使用一个exec函数),在调用main前先调用一个特殊的启动例程,而连接编译器
则由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数
安排。
进程终止:
正常返回:
1、从main返回
2、调用exit
3、调用_exit或者_EXIT
4、最后一个线程从其启动例程返回
5、从最后一个线程调用pthread_exit
异常终止3种方式:
6、调用abort
7、接到一个信号
8、最后一个线程对取消请求做出响应
如果将启动例程C代码形式表示(实际上该例程常常用汇编语言编写),则它调用main的形式可能是:
exit(main(argc,argv));
1、退出函数
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个退出函数都带一个整形参数,称为终止状态(或退出状态,exit status)。大多数UNIX系统shell都提供检查进程终止
状态的方法。如果(a)调用这些函数时不带终止状态,或(b)main执行了一个无返回值的returen语句,或(c)main没有
声明返回类型为整形,则该进程的终止状态是为定义的。但是,若main的返回类型是整形,并且main执行到最后一条语句时
返回,那么该进程的终止状态是0。
#include <stdio.h>
main()
{
printf("hello, world
");
}
对该程序进行编译,然后运行,则可见其终止码是随机的。如果在不同的系统上编译该程序,我们很可能得到不同的终止码,
这取决于main函数返回时栈和寄存器的内容:
执行它:
gcc -o hello hello.c
./hello
echo $?
2、函数atexit
按照ISO C的规定,一个进程可以登记多至32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序,并
调用atexit函数来登记这些函数:
#include <stdlib.h>
int atexit(void (*func)(void));
返回值:成功,返回0,失败,返回非0
其中,atexit的参数是一个函数地址,当调用此函数时无需向它传递任何参数,也不期望它返回一个值。
exit调用这些函数的顺序与它们登记时候的顺序相反。同一函数如若登记多次,也会被调用多次。
为了确定一个给定平台支持的最大终止处理程序数,可以使用sysconf函数。
根据ISO C和POSIX.1,exit首先调用各终止处理程序,然后关闭(通过fclose)所有打开流。
注意,内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式(通过调用exit)调用_exit或
_Exit。进程也可非自愿地由一个信号使其终止。
使用atexit函数:
#include <stdio.h>
#include <stdlib.h>
static void my_exit1(void);
static void my_exit2(void);
int main()
{
if(atexit(my_exit2)!=0)
printf("can't register my_exit2");
if(atexit(my_exit1)!=0)
printf("can;t register my_exit1");
if(atexit(my_exit1)!=0)
printf("can't register my_exit1");
printf("main is done
");
return(0);
}
static void my_exit1(void)
{
printf("first exit handler
");
}
static void my_exit2(void)
{
printf("second exit handler
");
}
第一个终止处理程序被登记两次,所以也会调用两次。注意,在main中没有调用exit,而是用了return语句。
命令行参数:
当执行一个程序时,调用exec的进程可将命令行参数传递给该新程序。这时UNIX shell的一部分常规操作。
程序将其所有的命令行参数都回显到标准输出上。注意,通常的echo程序不回显第0个参数:
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
int i;
for(i=0;i<argc;i++)
{
printf("argv[%d]:%s
",i,argv[i]);
}
exit(0);
}
ISO C和POSIX.1都要求argv[argc]是一个空指针。这就是我们可以将参数处理循环改写为:
for(i=0;argv[i]!=NULL;i++)
环境表:
每个进程都接收到一张环境表。与参数表一样,环境表也是一个字符指针数组,其中每个指针包含一个null结束的C字符串的
地址。全局变量environ则包含了该指针数组的地址:
extern char **environ;
例如,如果该环境包含5个字符串,那么它看起来就像:
environ:环境指针---->环境表: 环境字符串
HOME=/home/sar
PATH=:/bin:/usr/bin
SHELL=/bin/bash
USER=sar
LOGNAME=sar
NULL
其中每个字符串的结尾处都显示地由一个null字节。我们称environ为环境指针,指针数组为环境表,其中各指针指向的字符串
为环境字符串。
通常使用getenv和putenv函数来访问特定的环境变量,而不是用environ变量。但是如果要查看整个环境,则必须使用environ
指针。
C程序的存储空间布局:
历史沿袭至今,C程序一值由下列及部分组成:
1、正文段。这是由CPU执行的机器指令部分。通常,正文段时可共享的,所以即使频繁执行的程序(如文本编辑器、C编译器和
shell等)在存储器中也只需有一个副本,另外正文段常常时只读的,以防止程序由于意外而修改其指令。
2、初始化数据段。通常将此段称为数据段,它包含了程序中需明确地赋初值的变量。例如,C程序中任何函数之外的声明:
int maxcount=99;
使此变量以其初值存放在初始化数据段中。
3、末初始化数据段。通常将此段称为bss段,这一名称来源于早期汇编程序一个操作符,意思时"由符号开始的块",在程序开始
执行之前,内核将此段中的数据初始化为0或空指针。函数外的声明:
long sum[1000];
使此变量存放在非初始化数据段中。
4、栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址以及调用者的环境信息都
存放在栈中。然后,最近被调用的函数在栈上为其自动和历史变量分配存储空间。通过以这种方式使用栈,C递归函数可以工作。
递归函数每次调用自身时,就用一个新的栈帧,因此一个函数调用实例中的变量集不会影响另外一个函数调用实例中的变量。
5、堆。通常在堆中进行动态存储分配。堆位于未初始化数据段和栈之间。
[usrname@localhost 1]$ size /usr/bin/cc /bin/sh
text data bss dec hex filename
752679 8496 81856 843031 cdd17 /usr/bin/cc
901679 35984 22984 960647 ea887 /bin/sh
存储空间分配:
#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 *ptr);
环境变量:
#include <stdlib.h>
char *getenv(const char *name);
int putenv(char *str);
int setenv(const char *name,const char *value,int rewrite)
int unsetenv(const char *name);
函数setjmp和longjmp:
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env,int val);
返回值:若成功,返回0,若从longjmp返回非0
函数getrlimit和setrlimit:
每个进程都有一组资源限制,其中一些用getrlimit和setrlimit查询和更改:
#include <sys/resource.h>
int getrlimit(int resource,struct rlimit *rlptr);
int setrlimit(int resource,const struct rlimit *rlptr);
两个函数的返回值:成功0,出错非0
struct rlimit{
rlim_t rlim_cur;//soft limit
rlim_t rlim_max;//hard limit
}