天天看点

UNIX环境高级编程(第7章 进程环境)

1 C程序的启动与终止

C程序总是从main函数开始执行。main函数的原型:intmain(int argc, char *argv[]);当内核执行C程序时(使用一个exec函数),在调用main函数前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。

启动例程一般是用汇编语言实现,作用是使得从main函数返回后立即调用exit函数。等价代码为:

exit(main(argc,argv));

1)main函数是被启动例程调用,故从main函数return时,仍返回到启动例程中,main函数的返回值被启动例程得到,并作为参数调用exit函数。

2)也可以在main函数中调用exit函数直接终止进程而不返回到启动例程。

UNIX环境高级编程(第7章 进程环境)

    内核执行程序的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式地(通过调用exit)调用_exit或_Exit。进程也可非自愿地由一个信号使其终止。

    进程终止的方式有8种,其中5种为正常终止:

1)从main返回,即return

2)调用exit

3)调用_exit或_Exit

4)最后一个线程从其启动例程返回

5)最后一个线程调用pthread_exit

    异常终止的3种方式:

6)调用abort

7)接到一个信号并终止

8)最后一个线程对取消请求做出响应

1.1 exit函数

#include<stdlib.h>

void exit(int status);

void _Exit(int status);

#include<unistd.h>

void _exit(int status);

说明:

1)          参数:int status是终止状态

2)          功能:_exit和_Exit立即进入内核,exit则先执行一些清理处理(包括调用执行各终止处理程序,关闭所有标准I/O流等),然后进入内核。

1.2 atexit函数

        exit函数的功能在进入内核前,会自动调用执行终止处理程序。一个进程最多可以登记32个终止处理程序(终止处理程序即是函数),可通过调用atexit函数来登记这些函数。

#include <stdlib.h>

int atexit(void (*func)(void)); //成功返回0,出错返回非0

        exit调用这些func函数的顺序与它们登记时候的顺序相反。

#include "apue.h"

static void my_exit1(void);
static void my_exit2(void);

int main(void)
{
    if (atexit(my_exit2) != 0)
        err_sys("can't register my_exit2");
        
    if (atexit(my_exit1) != 0)
        err_sys("can't register my_exit1");

    printf("main is done\n");

    return 0;
}


static void my_exit1(void)
{
    printf("first exit handler\n");
}

static void my_exit2(void)
{
    printf("second exit handler\n");
}
           
[root]# ./a.out 
main is done
first exit handler
second exit handler
           

2参数变量与环境变量

    在历史上,大多数UNIX系统支持main函数带有3个参数,其中第二个参数是参数表,第三个参数是环境表。参数表与环境表都是字符指针数组。

int main(intargc, char *argv[], char *envp[]);

对于第三个参数环境表envp,全局变量environ包含了改指针数组的地址:

extern char**environ;

    因为ISO C规定main函数只有两个参数,而且第三个参数与全局变量environ相比也没用带来更多的益处,所以POSIX.1也规定应使用environ而不使用第三个参数。

2.1命令行参数

#include "apue.h"

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

    for (i = 0; i < argc; i++)
    {
        printf("argv[%d]: %s\n", i, argv[i]);
    } 

    exit(0);
}
           
[root]# ./a.out arg1 TEST foo
argv[0]: ./a.out
argv[1]: arg1
argv[2]: TEST
argv[3]: foo
           

2.2环境变量

    通过export(1)命令可查看当前所有的环境变量。

UNIX环境高级编程(第7章 进程环境)

    访问或设置环境变量不能直接使用全局变量environ,需要通过特定的函数:

#include<stdlib.h>

char *getenv(const char *name);

返回值:指向与name关联的value的指针,若未找到则返回NULL

#include<stdlib.h>

int putenv(char *str);

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

int unsetenv(const char *name);

返回值:若成功返回0,若出错返回非0值。

3 C程序的存储空间布局

32位操作系统环境下,当创建一个进程时,都会为该进程分配一个4GB大小的虚拟进程地址空间。该空间主要分为以下几个部分:

1)正文段:由CPU执行的机器指令部分。

        2)初始化数据段:即数据段,包含了程序中需明确地赋初值的变量。任何函数外的赋值声明,如int maxcount = 99;

        3)未初始化数据段:即bss段,在程序开始执行之前,内核将此段中的数据初始化为0或空指针。任何函数外的未赋值的声明,如long sum[1000];

        4)栈:自动变量以及每次函数调用时所需保存的信息都存放在此段中。

        5)堆:通常在堆中进行动态分配。

UNIX环境高级编程(第7章 进程环境)

4函数间跳转

C语言的goto语句能在一个函数内实现跳转功能,但要在函数间跳转——即在栈上跳过若干调用帧、返回到当前函数调用路径上的某一个函数中,则需要使用setjmp和longjmp函数。

头文件 #include <setjmp.h>
函数原型 int setjmp(jmp_buf env);
参数 env:某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息。因需要在另一个函数中引用env变量,故需将env变量定义为全局变量。
返回 若直接调用则返回0,若从longjmp调用返回则返回非0值。
功能 在希望返回到的位置调用setjmp
头文件 #include <setjmp.h>
函数原型 void longjmp(jmp_buf env, int val);
参数

env:在调用setjmp时所用的env

val:具有非0值,将是从setjmp处返回的值

返回 若直接调用则返回0,若从longjmp调用返回则返回非0值。
功能 返回到调用setjmp的位置

4.1自动、寄存器和易失变量

#include "apue.h"
#include <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, regiva = %d, "
                " volaval = %d, statval = %d\n",
                globval , autoval, regival, volaval, statval);
        exit(0);
    }

    globval = 95; autoval = 96; regival = 97; volaval = 98; statval = 99;

    f1(autoval, regival, volaval, statval);

    exit(0);

}

static void f1(int i, int j, int k, int l)
{
    printf("in f1(): \n");
    printf("globval = %d, autoval = %d, regiva = %d, "
            " volaval = %d, statval = %d\n",
            globval, i , j, k, l);
    f2();
}

static void f2(void)
{
    longjmp(jmpbuffer, 1);
}
           
[root]# gcc testjmp.c        /*不进行任何优化的编译*/
[root]# ./a.out 
in f1(): 
globval = 95, autoval = 96, regiva = 97,  volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 96, regiva = 97,  volaval = 98, statval = 99
[root]# gcc -O testjmp.c     /*进行全部优化的编译*/
[root]# ./a.out 
in f1(): 
globval = 95, autoval = 96, regiva = 97,  volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 2, regiva = 3,  volaval = 98, statval = 99 
           

4.2自动变量的潜在问题

声明自动变量的函数已经返回后,不能再引用这些自动变量。

#include <stdio.h>
#define DATAFILE "datafile"

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库函数仍将使用其流缓冲区的存储空间。这就产生了冲突和混乱。

    解决:在全局存储空间静态地(如static或extern)或者动态地(malloc)为数组databuf分配空间。