UNIX环境高级编程——进程环境

本文详细介绍了C程序的执行流程,从main函数开始,涵盖进程的生命周期,包括正常和异常终止的方式。讨论了命令行参数、环境表的使用,以及程序的存储空间布局,如正文段、数据段、BSS段、栈和堆。还涉及了动态内存分配函数malloc、calloc和realloc,以及共享库和存储空间的管理。此外,提到了函数setjmp和longjmp实现的跨函数跳转,以及资源限制的获取和设置。
摘要由CSDN通过智能技术生成

7.1 引言

本章主要讲解了进程的环境。

7.2 main函数

C程序总是从main函数开始执行,其函数原型为:

int main(int argc, char *argv[]);
  • argc是命令行参数的数目,argv是指向参数的各个指针所构成的数组;
  • 当内核执行C程序时(使用一个exec函数),在调用main函数前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的,而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。

7.3 进程终止

有8种方式使进程终止,其中5种为正常终止,它们是:

  • main返回;
  • 调用exit
  • 调用_exit_Exit
  • 最后一个线程从其启动例程返回;
  • 从最后一个线程调用pthread_exit

异常终止有3种方式,它们是:

  • 调用abort
  • 接到一个信号;
  • 最后一个线程对取消请求做出响应。

启动例程使得从main返回后立即调用exit函数。

退出函数

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函数返回一个整型值与用该值调用exit是等价的。

atexit函数

一个进程可以登记多至32个函数,这些函数将由exit自动调用,称为终止处理程序,用atexit函数来登记这些函数:

#include <stdlib.h>

int atexit(void (*func)(void));
										// 返回值:若成功,返回0;若出错,返回非0
  • atexit的参数是一个函数地址,当调用此函数时无需向它传递任何参数,也不期望它返回一个值;
  • exit调用这些函数的顺序与它们登记时候的顺序相反,同一函数如若登记多次,也会被调用多次。

exit首先调用各终止处理程序,然后关闭(通过fclose)所有打开流;如若程序调用exec函数族中的任一函数,则将清除所有已安装的终止处理程序。
在这里插入图片描述

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

7.4 命令行参数

  • 当执行一个程序时,调用exec的进程可将命令行参数传递给该新程序;
  • argv[argc]是一个空指针。

7.5 环境表

每个程序都接收到一张环境表,环境表是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址,全局变量environ则包含了该指针数组的地址,称之为环境指针

extern char **environ;

在这里插入图片描述

7.6 C程序的存储空间布局

C程序一直由下列几部分组成:

  • 正文段:这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是使用频繁执行的程序在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其指令;

  • 初始化数据段:通常将此段称为数据段,它包含了程序中需明确地赋初值的变量,例如,C程序中任何函数之外的声明:

    int maxcount = 99;
    

    此变量以其初值存放在初始化数据段中;

  • 未初始化数据段:通常将此段称为bss段(block started by symbol),在程序开始执行之前,内核将此段中的数据初始化为0空指针,函数外的声明:

    long sum[1000];
    

    此变量存放在非初始化数据段中;

  • 自动变量以及每次函数调用时所需保存的信息都存放在此段中;

  • :通常在堆中进行动态存储分配。
    在这里插入图片描述

由上图可以看出:

  • 栈从高地址向低地址方向增长;
  • 未初始化数据段的内容并不存放在磁盘程序文件中;
  • 需要存放在磁盘程序文件中的段只有正文段和初始化数据段。

7.7 共享库

  • 共享库使得可执行文件中不再需要包含公用的库函数,而只需在所有进程都可引用的存储区中保存这种库例程的一个副本;
  • 程序第一次执行或者第一次调用某个库函数时,用动态链接方法将程序与共享库函数相链接;
  • 共享库减少了每个可执行程序的长度,但增加了一些运行时间开销;
  • 共享库使得可以用库函数的新版本代替老版本而无需对使用该库的程序重新连接编辑。

7.8 存储空间分配

3个用于存储空间动态分配的函数:

  • malloc:分配指定字节数的存储区,此存储区中的初始值不确定;
  • calloc:为指定数量指定长度的对象分配存储空间,该空间中的每一位(bit)都初始化为0;
  • realloc:增加或减少以前分配区的长度,当增加长度时,可能需要将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值则不确定。
#include <stdlib.h>

void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
										// 3个函数返回值:若成功,返回非空指针;若出错,返回NULL
void free(void *ptr);
  • 特别地,若realloc的参数ptr为空指针,则realloc的功能与malloc相同。

7.9 环境变量

getenv函数用于获取环境变量值:

#include <stdlib.h>
char *getenv(const char *name);
										// 返回值:指向与name关联的value的指针;若未找到,返回NULL

以下3个函数用于设置环境变量:

#include <stdlib.h>
int putenv(char *ptr);
										// 返回值:若成功,返回0;若出错,返回非0
int setenv(const char *name, const char *value, int rewrite);
int unsetenv(const char *name);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • putenv取形式为name=value的字符串,将其放到环境表中,如果name已经存在,则先删除其原来的定义;
  • setenvname设置为value,如果在环境中name已经存在,那么:
    (a)若rewrite非0,则首先删除其现有的定义;
    (b)若rewrite为0,则不删除其现有定义(name不设置为新的value,而且也不出错);
  • unsetenv删除name的定义,即使不存在这种定义也不算出错。

7.10 函数setjmp和longjmp

在C中,goto语句是不能跨越函数的,而执行这种类型跳转功能的是函数setjmplongjmp,它们在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中:

#include <setjmp.h>

int setjmp(jmp_buf env);
										// 返回值:若直接调用,返回0;若从longjmp返回,则为非0
void longjmp(jmp_buf env, int val);
  • 在希望返回到的位置调用setjmp
  • setjmp参数env的类型是一个特殊类型jmp_buf,是某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息,因为需要在另一个函数中引用env变量,所以通常将env变量定义为全局变量
  • 当检查到一个错误时,以两个参数调用longjmp函数,第一个是在调用setjmp时所用的env,第二个是具非0值的val,它将成为从setjmp处返回的值。

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

  • 如果不想让自动变量值回滚,可定义其为具有volatile属性;
  • 声明为全局变量或静态变量的值在执行longjmp时保持不变。

自动变量的潜在问题

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

7.11 函数getrlimit和setrlimit

每个进程都有一组资源限制,进程的资源限制通常是在系统初始化时由0进程建立的,然后由后续进程继承:

#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: current limit */
	rlim_t	rlim_max;	/* hard limit: maximum value for rlim_cur */
};

在更改资源限制时,须遵守下列3条规则:

  • 任何一个进程都可将一个软限制值更改为小于或等于其硬限制值;
  • 任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值,这种降低,对普通用户不可逆;
  • 只有超级用户进程可以提高硬限制值。

这两个函数的resource参数取下列值之一:
在这里插入图片描述

7.12 实例代码

chapter7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MinBadGuy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值