APUE 第七章 进程的运行环境

来源:互联网 发布:蚂蜂窝app无网络 编辑:程序博客网 时间:2024/05/20 06:28

APUE第七章主要分享了进程的运行环境。主要内容包括:

1、main函数

在这节里面主要说明了在我们平常利用的main函数是如何被调用的。其实从程序开发人员的角度会考虑这样一个问题,编译后 的程序为什么会运行?为什么要有main函数等等。main函数是不是程序最开始运行的地方,等等。

其实当一个C程序在运行前,系统会做很多的初始化工作,然后再调用exec函数来运行C程序。

2、进程的终止

          • 进程的终止方式有8种:
          • 其中正常终止有5种:
                    •       return from main
            •       调用exit函数
        •       调用_exit或者_Exit函数
        •        return of the last thread from its start routine
        •        call pthread_exit from the last thread

异常终止有3种:

      •       调用abort
  •        receipt of a signal(收到信号)
  •        response of the last thread to a cancellation request

接下来有三个exit函数:

#include<stdlib.h>

void exit(int status);

void _Exit(int status);

#include<unistd.h>

void _exit(int status);

其中_Exit和_exit函数会理解返回到kernel;而exit函数会做一些清理工作,比如调用fclose关闭文件等,将未写入的文件flush掉等,然后会返回kernel。

这三个函数都有一个int的参数,称为exit status。大多数的shell可以检测进程退出时的状态。如果在main函数中没有调用return、exit或者return了但没有指定return的值,那么进程的exit status是不确定的。但在C99里面,进程的退出状态为0.

eg:

#include<stdio.h>

main()

{

printf("hello, world/n");

}

编译,链接,运行程序。

$ ./a.out

hello, world

$echo $? //用于输出进程的退出状态

13//可以看到退出状态是13,其实是printf函数的返回值

而如果指定是C99方式的话,即

$ cc -std=c99 hello.c //指明是C99

hello.c:4 warning: return type defaults to int

$ ./a.out

hello, world

$echo $?

0

可以看到C99标准下退出状态是0

曾经看到一道笔试题,说main函数在调用结束的时候会不会有什么动作,大概是这个意思吧。其实就是考的如下函数

#include<stdlib.h>

int atexit(void (*func) (void));

在ISO C中一个进程可以注册至多32个函数,这些函数是在进程exit的时候调用的。同时这些函数调用的顺序和他们注册的顺序相反,有点类似C++中构造函数一样。

下面这张图展示了一个C程序的如何开始和结束的:

clip_image001

从上图可知,kernel调用exec函数族来运行C程序,在C程序运行过程中会调用其他的函数,然后C程序可能会调用exit系列或者return(其实是间接调用exit)来结束自己的运行。可以看到,在调用exit函数时,程序不会理解返回到kernel中,而是做了一系列的清理工作,然后调用_exit或者_Exit函数返回kernel的。

3、命令行参数

在调用exec函数族的时候可以传递给进程一系列的参数。

4、环境列表

环境列表主要是在全局变量char **environ中存储,形式是name=value,可以从下图看出:

clip_image002

一般来说,获取环境变量不是通过直接操作environ变量来进行的,而是通过调用getenv和setenv函数来进行的。

5、C程序在内存中的布局

clip_image003

6、shared library

库文件的使用大大减少了可执行文件的大小,同时当库内部函数的具体实现改变时,只要接口不改变,不会影响库的使用。库文件一个缺点是当系统第一次调用库文件时开销会大一点。

7、内存分配

这里说的内存主要是指的是堆空间的分配。堆空间在使用的时候需要特别注意,需要开发人员自己申请,同时也需要自己释放不再需要的空间,如果不正确释放堆空间,那么会照成memory leak,即内存泄漏。

堆空间分配的函数主要有:

#include<stdlib.h>

void *malloc(size_t size);

//malloc用来分配size大小的堆空间,同时分配空间的初始值是不确定的!!

void *calloc(size_t nobj, size_t size);

//calloc用来分配nobj个大小为size的空间,即分配nobj*size大小的空间,但不同于malloc的是calloc分配的空间是初始化过的!

void *realloc(void *ptr, size_t newsize);

//realloc用来增长或者减少之前分配的空间。需要注意的是ptr指向的空间必须是通过malloc或者calloc分配的空间!!如果ptr为NULL,那么realloc与malloc类似,用来分配newsize大小的空间。

//return NULL on error. non-null is OK

void free(void *ptr);

//释放堆空间。其中ptr必须是malloc、calloc或者realloc调用后的返回值,即必须是动态内存的分配才可以使用free释放。

可以利用lint和valgrind等检测工具来检测程序中的隐蔽bug,将这两个工具结合起来很是给力~

8、环境变量

从前面的介绍中可以看到,环境变量的形式是name=value。一般来说不是直接操作environ来进行获取或设置,而是通过getenv或者setenv函数来获取和操作的。

#include<stdlib.h>

char *getenv(const char *name);

//通过name来获得对应的value。

我们可以改变当前进程和他子进程的运行环境,但是不能改变当前进程父进程的运行环境。

9、setjmp和longjmp函数

在C语言中,goto语句一般是不提倡使用的,但如果研究一些源代码会发现,其实很多代码里面都使用的goto。但是goto语句有一个不足的地方就是他不能跳出函数的范围,只能在一个函数内部进行。标准库提供了setjmp和longjmp函数来结合使用,可以实现长跳转。

一般来说setjmp和longjmp函数用于一些出错处理,其他方面用的不多,很多地方都建议尽量少使用。

可参考《APUE》和《c专家编程》

10、getrlimit和setrlimit函数

每一个进程都有一系列资源的限制,例如进程最多可以打开的文件数、进程堆栈的大小等。这些limit可以通过getrlimit和setrlimit函数来进行获取和设置。

#include<sys/resource.h>

int getrlimit(int resource, struct rlimit *rlptr);

int setrlimit(int resource, const struct rlimit *rlptr);

//return 0 if ok; nonzero on error

其中rlimit是这样一个结构:

struct rlimit

{

rlim_t rlim_cur;//soft limit: 表示当前的limit值

rlim_t rlim_max;//hard limit:表示rlim_cur的最大值

};

一个进程有如下操作:

            •       可以改变soft limit,soft limit必须要小于等于hard limit
        •       可以改变hard limit,但必须要保证hard limit大于等于soft limit
        •       只有超级用户可以改变hard limit

如果某个limit是没有限制的话,那么会被指定为常量:RLIM_INFINITY

下图是一些常见的limit常量和他们的具体含义:

clip_image004

clip_image005

其中在调试程序时用到的一个是core的大小,一般来说可能系统设置core的大小是0,即当程序异常退出时不产生core,这对于调试来说不太实用,因此一般需要调整该值。在bash中,有这样一个命令:ulimit可以改变core文件的大小。

一般是通过:

ulimit -c unlimited来使得core文件的大小不受限制。当然也有其他的选项。

上面大概就是APUE第七章进程运行环境的一个总结,有很多细节需要在设计中深入考虑。

原创粉丝点击