APUE学习笔记:第七章 进程环境

7.1 引言

本章将学习:当执行程序时,其main函数是如何被调用的;命令行参数是如何传送给执行程序的;典型的存储器布局是什么样式;如何分配另外的存储空间;进程如何使用环境变量;各种不同的进程终止方式等;另外还将说明longjmp和setjmp函数以及它们与栈的交互作用;还将介绍研究进程的资源限制

7.2 main函数

C程序总是从main函数开始执行。当内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的,而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排

7.3 进程终止

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

(1)从main返回

(2)调用exit

(3)调用_exit或_Exit

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

(5)最后一个线程调用pthread_exit

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

(6)调用abort

(7)接到一个信号并终止

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

上一节的启动例程是这样编写的,使得从main返回后立即调用exit函数:exit(main(argc,argv));

1.exit函数

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

#include<stdlib.h>    //ISO

void exit(int status);

void _Exit(int status);

#include<unistd.h>    //POSIX.1

void _exit(int status);

由于历史原因,exit函数总是执行一个标准I/O库的清理关闭操作:为所有打开流调用fclose函数。这回造成所有缓冲的输出数据都被冲洗(写到文件上)

三个函数都带一个整型参数,称之为终止状态。大多数UNIX shell都提供检查进程终止状态的方法。如果(a)若调用这些函数时不带终止状态,或(b)main执行了一个无返回值的return语句,或(c)main没有声明返回类型为整型,则该进程的终止状态是未定的。

2.atexit函数

按照ISO C的规定,一个进程可以登记多达32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序,并调用atexit函数来登记这些函数

#include<stdlib.h>

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

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

其中,atexit的参数是一个函数地址,当调用此函数时无需向它传送任何参数,也不期望它返回一个值。exit调用这些函数的顺序与它们登记时候的顺序相反。同一函数如若登记多次,则也会被调用多次。

内核使程序执行的唯一方法是调用一个exec函数

实例:7_2 终止处理程序实例

 1 #include"apue.h"
 2
 3 static void my_exit1(void);
 4 static void my_exit2(void);
 5
 6 int main()
 7 {
 8     if(atexit(my_exit2)!=0)
 9         err_sys("can‘t register my_exit2");
10     if(atexit(my_exit1)!=0)
11         err_sys("can‘t register my_exit1");
12     if(atexit(my_exit1)!=0)
13         err_sys("can‘t register my_exit1");
14
15     printf("main is done\n");
16     return(0);
17 }
18 static void my_exit1(void)
19 {
20     printf("first exit handler\n");
21 }
22 static void my_exit2(void)
23 {
24     printf("second exit handler\n");
25 }

执行后可以看出:终止处理程序每登记一次,就会被调用一次,且exit调用这些函数的顺序与它们登记时候的顺序相反。

7.4 命令行参数

当执行一个程序时,调用exec的进程可将命令行参数传递给该新进程。

实例:7_3 将所有命令行参数回送到标准输出

#include"apue.h"
int main(int argc,char *argv[])
{
    int i;
    for(i=0;i<argv;i++)
    printf("argv[%d]:%s\n",i,argv[i]);
    exit(0);
}

7.5 环境表

每个程序都会接收到一张环境表。与参数表一样,环境表也是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址。全局变量environ则包含了该指针数组的地址:extern char **environ;

 

7.6 C程序的存储空间布局

从历史上讲,C程序一直由下面几部分组成:

-正文段。这是由cpu执行的机器指令部分。通常,正文段是可共享的。所以即使频繁执行的程序(如文本编辑器,C编译器和shell等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其自身指令

-初始化数据段。通常将此段称为数据段,它包含了程序中需明确地赋初值的变量。例如,C程序中出现在任何函数之外的声明:int maxcount=99,使此变量带有其初值存放在初始化数据段中

-非初始化数据段。通常将此段称为bss段,这一名称来源于一个早期的汇编运算符,意思是“block started by symblo”(由符号开始的块),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。出现在任何函数外的C声明:long sum[1000];使此变量存放在非初始化数据段中

-栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次调用函数时,其返回地址以及调用者的环境信息(例如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,可以递归调用C函数。递归函数每次调用自身时,就使用一个新的栈帧,因此一个函数调用实例中的变量集不会影响另一个函数调用实例中的变量

-堆。通常在队中进行动态存储分配。由于历史形成的惯例,堆位于非初始化数据段和栈之间

size(1)命令报告正文段、数据段和bss段的长度

7.7 共享库

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

 

7.8 存储器分配

ISO C说明了三个用于存储空间动态分配的函数

(1)malloc :分配指定字节数的存储区。此存储区中的初始值不确定。

(2)calloc  :为指定数量具指定长度的对象分配存储空间。该控件中的每一位都初始化为0

(3)realloc:更改以前分配区的长度(增加或减少)。当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值        则不确定

#include<stdlib.h>

void *malloc(size_t size);

void *calloc(size_t nobj,size_t size);

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

                //三个函数返回值:若成功则返回非空指针,若出错则返回NULL
void free(void *ptr);

这三个分配函数多返回的指针一定是适当对齐的,使其可用于任何数据对象。

函数free释放ptr指向的存储空降。被释放的空间通常被送入可用存储区池,以后,可在调用上述三个分配函数再分配。

7.9 环境变量

ISO C定义了一个函数getenv,可以用其取环境变量值,但是该标准又称环境的内容是由实现定义的。

#include<stdlib.h>

char *getenv(const char *name);

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

注意,此函数返回一个指针,指向name=value字符串中的value。我们应当使用getenv从环境中取一个指定环境变量的值,而不是直接访问environ

#include<stdlib,h>

int putenv(char *str);

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

int unsetenv(const char *name);

            //三个函数返回值:若成功则返回0,若出错则返回非0值

这三个函数的操作是:

-putenv取形式为name=value的字符串,将其放到环境表中。如果name已经存在,则先删除其原理的定义。

-setenv将name设置为value。如果在环境中name已经存在,那么(a)若rewrite非0,则首先删除其现有定义;(b)若rewrite为0,则不删除其现有定义

-unsetenv删除name的定义。即使不存在这种定义也不出错

如果修改一个现有的name:

a.如果新value的长度少于或等于现有value的长度,则只要在原字符串所用空间中写入新字符串

b.如果新value的长度大于原长度,则必须调用malloc为新字符串分配空间,然后将新字符串复制到该空间中,接着使环境表中针对name的指针指向新分配区

如果要增加一个新的name:

a.如果这是第一次增加一个新name,则必须调用malloc为新的指针表分配空间。接着,将原来的环境表复制到新分配区,并将指向新name=value字符串的指针存放在该指针表的表尾,然后又将一个空指针存放在其后。最后使environ指向新指针表。

b.如果这不是第一次增加一个新name,则可知以前调用malloc在堆中为环境表分配了空间,所以只要调用realloc,以分配比原空间多存放一个指针的空间。然后将指向新name=value字符串的指针存放在该表表尾,后面跟着一个空指针

7.10 setjmp和longjmp函数

在C中,goto语句是不能跨越函数的,而执行这类跳转功能的是函数setjmp和longjmp。这两个函数对于处理发生在深层嵌套函数调用那个中的出错情况是非常有用的

实例:7_4  进行命令处理的典型程序骨架

 1 #include"apue.h"
 2
 3 #define TOK_ADD 5
 4
 5 void do_line(char *);
 6 void cmd_add(void);
 7 int get_token(void);
 8
 9 int main()
10 {
11     char line[MAXLINE];
12     while(fgets(line,MAXLINE,stdin)!=null)
13         do_line(line);
14     exit(0);
15 }
16 char *tok_ptr;  //global pointer for get_token()
17
18 void do_line(char *ptr)    //process one line of input
19 {
20     int cmd;
21     tok_ptr=ptr;
22     while((cmd=get_token())>0){
23     switch(cmd){//one case for each command
24     case TOK_ADD:
25         cmd_add();
26         break;
27     }
28     }
29 }
30 void cmd_add(void)
31 {
32     int token;
33     token=get_token();
34     //rest of processing for this command
35 }
36 int get_token(void)
37 {
38     //fetch next token from line pointed to by tok_ptr
39 }

其主循环是从标准输入读一行,然后调用do_line处理该输入行。do_line函数调用get_token从该输入行中取下一个标记。一行中的第一个标记假定是一条某种形式的命令,于是switch语句就实现命令选择。

#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变量定义为全局变量

实例:7_5 setjmp和longjmp实例

http://blog.163.com/[email protected]/blog/static/1618444162011529103634600/

可直接参考上述链接,总的来说,就是setjmp设置跳转的位置,longjmp跳到那个位置,但跳转只是回来原来的位置,并不实现回滚自动变量和寄存器的值(在上面代码中加上一个静态全局变量,不断自增输出,即可看出)

7.11 getrlimit 和setrlimit函数

每个进程都有一组资源限制,其中一些可以用getrlimit和setrlimit函数查询和更改。

#include<sys/resource.h>

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

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

对这两个函数的每一次调用都会指定一个资源以及一个指向下列结构的指针

struct rlimit{
    rlim_t rlim_cur;//soft limit:current limit
    rlim_t rlim_max;//hard limit:maximum value for rlim_cur;

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

(1)任何一个进程都可将一个软限制更改为小于或等于其硬限制值

(2)任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低对普通用户而言是不可逆的

(3)只有超级用户进程可以提高硬限制值

常量RLIM_INFINITY指定了一个无限量的限制

实例:7_8 打印当前资源限制

 1 #include"apue.h"
 2 #if defined(BSD)||defined(MACOS)
 3 #include<sys/time.h>
 4 #define FMT "%10lld "
 5 #else
 6 #define FMT "%10ld "
 7 #endif
 8 #include<sys/resource.h>
 9
10 #define doit(name) pr_limits(#name,name)
11 static void pr_limits(char *,int);
12 int main(void)
13 {
14     #ifdef RLIMIT_AS
15     doit(RLIMIT_AS);
16     #endif
17     doit(RLIMIT_CORE);
18     doit(RLIMIT_CPU);
19     doit(RLIMIT_DATA);
20     doit(RLIMIT_FSIZE);
21     #ifdef RLIMIT_LOCK
22     doit(RLIMIT_LOCKS);
23     #endif
24     #ifdef    RLIMIT_MEMLOCK
25     doit(RLIMIT_MEMLOCK);
26     #endif
27     doit(RLIMIT_NOFILE);
28     #ifdef RLIMIT_NPROC
29     doit(RLIMIT_NPROC);
30     #endif
31     #ifdef RLIMIT_RSS
32     doit(RLIMIT_RSS);
33     #endif
34     #ifdef RLIMIT_SBSIZE
35     doit(RLIMIT_SBSIZE);
36     #endif
37     doit(RLIMIT_STACK);
38     #ifdef RLIMIT_VMEM
39     doit(RLIMIT_VMEM);
40     #endif
41     exit(0);
42 }
43 static void pr_limits(char *name,int resource)
44 {
45     struct rlimit limit;
46     if(getrlimit(resource,&limit)<0)
47     err_sys("getrlimit error for %s",name);
48     printf("%-14s ", name);
49     if(limit.rlim_cur==RLIM_INFINITY)
50     printf("(infinite) ");
51     else
52     printf(FMT,limit.rlim_cur);
53     if(limit.rlim_max==RLIM_INFINITY)
54     printf("(infinite)");
55     else
56     printf(FMT,limit.rlim_max);
57     putchar((int)‘\n‘);
58 }

APUE学习笔记:第七章 进程环境,布布扣,bubuko.com

时间: 2024-10-25 07:42:00

APUE学习笔记:第七章 进程环境的相关文章

APUE学习笔记:第九章 进程关系

9.1 引言 本章将更详尽地说明进程组以及POSIX.1引入的会话的概念.还将介绍登陆shell(登录时所调用的)和所有从登陆shell启动的进程之间的关系. 9.1 终端登陆 系统管理员创建通常名为/etc/ttys的文件,其中每个终端设备都有一行,每一行说明设备名传递给getty程序的参数.当系统自举时,内核创建进程ID为1的进程,依旧是init进程.init进程使系统进入多用户状态.init进程读文件/etc/ttys,对每一个允许登陆的终端设备,init调用一次fork,所生成的子进程则

apue学习笔记(第九章 进程关系)

本章将详细地说明进程组以及POSIX.1引入的会话的概念.还将介绍登录shell和所有从登录shell启动的进程之间的关系 终端登录 BSD终端登录.系统管理者创建通常名为/etc/ttys的文件,其中每个终端设备都有一行,用来说明设备名和传到getty程序的参数. 当系统自举时,内核创建进程ID为1的进程(init进程).init进程读取文件/etc/ttys,对每一个允许登录的终端设备调用一次fork,它所生成的子进程则exec getty程序,如下图所示: getty对终端设备调用open

APUE学习笔记:第一章 UNUX基础知识

1.2 UNIX体系结构 从严格意义上,可将操作系统定义为一种软件(内核),它控制计算机硬件资源,提供程序运行环境.内核的接口被称为系统调用.公用函数库构建在系统调用接口之上,应用软件即可使用公用函数库,也可使用系统调用.shell是一种特殊的应用程序,它为运行其他应用程序提供了一个接口 从广义上,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并给予计算机以独有的特性(软件包括系统实用程序,应用软件,shell以及公用函数库等) 1.3  shell shell是一个命令行解

APUE学习笔记:第二章 UNIX标准化及实现

2.2UNIX标准化 2.2.1 ISO C 国际标准化组织(International Organization for Standardization,ISO) 国际电子技术委员会(International Electrotechnical Commission,IEC) ISO C标准的意图是提供C程序的可移植性,使其能适合于大量不同的操作系统,而不只是UNIX系统.此标准不仅定义了C程序设计语言的语法和语义,还定义了其标准库.因为所有现今的UNIX系统都提供C标准中定义的库例程,所以该

APUE学习笔记:第八章 进程控制

8.1 引言 本章介绍UNIX的进程控制,包括创建新进程.执行程序和进程终止.还将说明进程属性的各种ID-----实际.有效和保存的用户和组ID,以及他们如何受到进程控制原语的影响.本章还包括了解释器文件和system函数.本章最后讲述大多数UNIX系统所提供的进程会计机制.这种机制使我们能够从另一个角度了解进程的控制功能. 8.2 进程标识符 每个进程都有一个非负整型表示的惟一进程ID.因为进程标识符是惟一的,常将其用作其他标识符的一部分以保证其惟一性.虽然是惟一的,但是进程ID可以重用.(大

《Unix环境高级编程》读书笔记 第7章-进程环境

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

Python学习笔记__10.4章 进程VS线程

# 这是学习廖雪峰老师python教程的学习笔记 1.概览 我们介绍了多进程和多线程,这是实现多任务最常用的两种方式.现在,我们来讨论一下这两种方式的优缺点 要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker. 如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker. 如果用多线程实现Master-Worker,主线程就是Master,其

Android学习笔记—第五章 进程与线程

第五章 进程与线程 进程:一个应用程序就是一个进程 (1)进程的优先级: Foreground Process 前台进程 a. 当前用户正在操作的Activity所在的进程 b. 绑定了当前用户操作的Activity的service所在的进程 c. 通过调用了startForeground()方法提升优先级的service所在的进程 d. 正在调用onCreate().onStart().onDestory()方法的service所在的进程 e. 正在调用onReceiver()方法的Broad

Java学习笔记—第七章 类的深入解析

第七章 类的深入解析 1. 继承 1.1 类继承的方法:在Java中,子类对父类的继承是在类的声明中使用extends关键字来指明的.其一    般格式为:[类修饰符] class <子类名> extends <父类名>{ 类体内容 }.一个类只能直接继承一个    父类,一个父类可以有多个子类. 1.2 成员变量的继承和隐藏:基于父类创建子类时,子类可以继承父类的成员变量和成员方法.但是,     如果在父类和子类中同时声明了一个同名变量,则这两个变量在程序运行时同时存在.即:父