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

1. main函数

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

2. 进程终止

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

    1. 从main返回
    2. 调用exit
    3. 调用_exit或_Exit
    4. 最后一个线程从其启动例程返回
    5. 从最后一个线程调用pthread_exit
  • 异常终止有3种方式:
    1. 调用abort
    2. 接到一个信号
    3. 最后一个线程对取消请求做出响应

2.1 退出函数

  1. #include <stdlib.h>
  2. void exit(int status);
  3. void _Exit(int status);
  4. #include <unistd.h>
  5. void _exit(int status);
  • _exit(由POSIX.1说明)和_Exit(由ISO C说明)立即进入内核,而exit(由ISO C说明)则先执行一些清理处理,然后返回内核。
  • 3个退出函数都带一个整型参数,称为终止状态退出状态。大多数Unix系统shell都提供检测进程终止状态的方法。
  • 下面几种情况下,该进程的终止状态是未定义的:
    1. 调用这些函数时不带终止状态
    2. main执行了一个无返回值的return语句
    3. main没有声明返回类型为整型

2.2 函数atexit

  1. #include <stdlib.h>
  2. int atexit(void (*func)(void)); // 参数是一个函数指针
  3. Returns: 0 if OK, nonzero on error
  • 按照ISO C的规定,一个进程可以登记多至32个函数,这些函数将由exit自动调用。称这些函数为终止处理程序
  • exit首先调用各终止处理程序,然后关闭所有打开流。exit调用这些函数的顺序与它们登记的顺序相反。
  • 如若程序调用exec函数,则将清除所有已安装的终止处理程序。
  • 注意:内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式(通过调用exit)地调用_exit或_Exit。进程也可非自愿地由一个信号使其终止。
  • 一个C程序的启动,以及它的各种终止方式

3. 命令行参数

  • 当执行一个程序时,调用exec的进程可将命令行参数传递给新程序。这是Unix shell的一部分常规操作。

4. 环境表

  • extern char **environ; 称environ为环境指针;指针数组为环境表,其中各指针指向的字符串为环境字符串

5. C 程序的存储空间布局

  • 历史沿袭至今,C程序一直由以下几部分组成:

    1. 正文段。这是由CPU执行的机器指令部分。通常,正文段是可由多个应用程序共享的,在存储器中只需一个副本。正文段常常是只读的。
    2. 初始化数据段,也称为数据段。包含了程序中需明确地赋初值的变量。
    3. 未初始化数据段,也称为bss段(block started by symbol,由符号开始的块)。在程序开始执行之前,内核将此段中的数据初始化为0或空指针。
    4. 栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。
    5. 堆。通常在堆中进行动态存储分配。惯例,堆位于未初始化数据段和栈之间。
  • 一种典型安排方式,这是程序的逻辑布局
  • size命令报告正文段、数据段和bss段的长度(以字节为单位)

    $ size /usr/bin/cc /bin/sh
    text data bss dec hex filename
    346919 3576 6680 357175 57337 /usr/bin/cc
    102134 1776 11272 115182 1c1ee /bin/sh
    第4、5列分别是以十进制和十六进制表示的3段总长度

6. 共享库

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

7. 存储空间分配

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

    malloc,初始值不确定
    calloc,每一位都初始化为0
    realloc,新增区域内的初始值不确定

  1. #include <stdlib.h>
  2. void *malloc(size_t size);
  3. void *calloc(size_t nobj, size_t size);
  4. void *realloc(void *ptr, size_t newsize);
  5. All three return: non-null pointer if OK, NULL on error
  6. void free(void *ptr);
  • 这3个分配函数所返回的指针一定是适当对齐的,满足最苛刻的对齐要求,使其可用于任何数据对象。
  • 返回通用指针void *
  • 这些分配例程通常用sbrk系统调用实现。该系统调用扩充或缩小进程的堆。虽然sbrk可以扩充或缩小进程的存储空间,但是大多数malloc和free的实现都不减少进程的存储空间。释放的空间可供以后再分配,将它们保持在malloc池中而不返回给内核。
  • 大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度、指向下一个分配块的指针等。
  • 常见错误:1. 释放一个已经释放了的块;2. 调用free时所用的指针不是3个alloc函数的返回值;3. 忘记调用free造成内存泄露。

8. 替代的存储空间分配程序

  • libmalloc
  • vmalloc
  • quick-fit,历史上所使用的标准malloc算法是最佳适配或首次适配存储分配策略。quick-fit算法比上述两种快,但可能使用较多存储空间
  • jemalloc
  • TCMalloc,高性能、高扩展性和高存储效率。开源,是Google-perftools工具中的一个
  • 函数alloca,在当前函数的栈帧上分配存储空间,而不是在堆上。

    优点:当函数返回时,自动释放它所使用的栈帧,不再需要自己释放空间
    缺点:alloca函数增加了栈帧的长度,而某些系统在函数已经被调用后不能增加栈帧长度,于是也就不能支持alloca函数。本书4个平台均支持该函数。

9. 环境变量

  • Unix内核并不查看这些字符串,它们的解释完全取决于各个应用程序。
  • LANG、PATH、HOME、PWD、TERM ...
  1. #include <stdlib.h>
  2. char *getenv(const char *name);
  3. Returns: pointer to value associated with name, NULL if not found
  1. #include <stdlib.h>
  2. int putenv(char *str); // name已存在,替换
  3. Returns: 0 if OK, nonzero on error
  4. int setenv(const char *name, const char *value, int rewrite); // 当name已存在时,根据rewrite参数而覆盖或保持原值
  5. int unsetenv(const char *name); // 即使name不存在也不算出错
  6. Both return: 0 if OK, −1 on error
  • 这些函数在修改环境表时是如何进行操作的呢?

    1. 删除一个字符串很简单
    2. 但是增加一个字符串或修改一个现有的字符串就困难得多。因为环境表和环境字符串通常占用的是进程地址空间的顶部,故不能向上扩展;而其下方是各栈帧,故也不能向下扩展。两者组合使得该空间的长度不能再增加。

19. 函数setjmp、longjmp

  • 在C中,goto语句是不能跨越函数的,而执行这种类型的跳转功能是函数setjmp和longjmp。它们对于处理发生在很深层嵌套函数调用中的出错情况是非常有用的
  1. #include <setjmp.h>
  2. int setjmp(jmp_buf env);
  3. Returns: 0 if called directly, nonzero if returning from a call to longjmp
  4. void longjmp(jmp_buf env, int val);
  • 直接调用setjmp,返回0;调用longjmp导致程序在setjmp处返回,返回值为val
  • 特殊类型jmp_buf,是某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息。

19.1 自动变量、寄存器变量、易失变量volatile

  • 当调用longjmp函数后,在main函数中,自动变量和寄存器变量的状态如何?

    1. 看情况。大多数实现并不回滚这些自动变量和寄存器变量的值,而所有标准则称它们的值是不确定的。
    2. 如果有一个自动变量,而不想其值回滚,可定义为具有volatile属性。
    3. 声明为全局变量或静态变量的值在执行longjmp时保持不变。

19.2 自动变量的潜在问题

  • 在某个函数调用返回后,仍然在使用分配在该函数栈上的变量。

20. 函数getrlimit、setrlimit

  1. #include <sys/resource.h>
  2. int getrlimit(int resource, struct rlimit *rlptr);
  3. int setrlimit(int resource, const struct rlimit *rlptr);
  4. Both return: 0 if OK, −1 on error
  1. struct rlimit {
  2. rlim_t rlim_cur; /* soft limit: current limit */
  3. rlim_t rlim_max; /* hard limit: maximum value for rlim_cur */
  4. };
  • 在更改资源限制时,须遵循下列3条规则:

    1. 任何一个进程都可将一个软限制更改为小于或等于其硬限制值
    2. 任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低,对普通用户而言是不可逆的
    3. 只有超级用户进程可以提高硬限制值。常量RLIM_INFINITY为无限制
  • resource参数的取值列举:

    RLIMIT_AS 进程总的的可用存储空间的最大长度(字节)
    RLIMIT_CORE core文件的最大字节数
    RLIMIT_FSIZE 可以创建的文件的最大字节长度
    RLIMIT_NOFILE 每个进程能打开的最多文件数
    RLIMIT_NPROC 每个实际用户ID可拥有的最大子进程数
    RLIMIT_STACK 栈的最大字节长度

原创文章,转载请声明出处:http://www.cnblogs.com/DayByDay/p/3911195.html

《Unix环境高级编程》读书笔记 第7章-进程环境,布布扣,bubuko.com

时间: 2024-10-14 04:12:22

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

UNIX环境高级编程学习笔记(第一章UNIX基础知识)

总所周知,UNIX环境高级编程是一本很经典的书,之前我粗略的看了一遍,感觉理解得不够深入. 听说写博客可以提高自己的水平,因此趁着这个机会我想把它重新看一遍,并把每一章的笔记写在博客里面. 我学习的时候使用的平台是Windows+VMware+debian,使用secureCRT来连接(可以实现多个终端连接). 因为第一章是本书大概的描述,所以第一章的我打算写得详细一点,而且书本的原话占的比例会比较多,重点的东西会用粗体显示出来. 1.1  引言 所有操作系统都为他们所运行的程序提供服务.典型的

Windows核心编程读书笔记-第四章进程

1.进程组成 一个内核对象,操作系统用它来管理进程. 一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据.此外,它还包含动态内存分配,比如线程堆栈和堆的分配. 2.一个进程可以有多个线程,所有线程都在进程的地址空间中"同时"执行代码.每个进程至少要有一个线程来执行进程地址空间包含的代码. 3.用Microsoft Visual Studio来创建一个应用程序项目时,集开发环境会设置各种链接器开关,使链接器将子系统的正确类型嵌入最终生成的可执行文件.对于CUI程序,这个链接器开

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

1. 进程标识 进程ID标识符是唯一.可复用的.大多数Unix系统实现延迟复用算法,使得赋予新建进程的ID不同于最近终止所使用的ID ID为0的进程通常是调度进程,也常被称为交换进程.它是内核的一部分,是系统进程. ID为1的进程通常是init进程,在自举过程结束时由内核调用.该进程负责在内核自举后启动一个Unix系统,它决不会终止,是一个普通的用户进程,但以超级用户特权运行. ID为2的进程是页守护进程,负责支持虚拟存储器系统的分页操作. #include <unistd.h> pid_t

Unix环境高级编程学习笔记(五):进程控制

1 getpid函数,getppid函数,得到进程id,得到父进程id #include<unistd.h> pid_t getpid(void) pid_t getppid(void) uid_t getuid(void)得到实际用户id uid_t geteuid(void)得到有效用户id gid_t getgid(void)得到实际组id gid_t getegid(void)得到有效组id 2 fork函数,当前进程创建新进程 #include<unistd.h> pid

《UNIX环境高级编程》笔记——3.文件IO

一.引言 说明几个I/O函数:open.read.write.lseek和close,这些函数都是不带缓冲(不带缓冲,只调用内核的一个系统调用),这些函数不输入ISO C,是POSIX的一部分: 多进程共享资源(包括文件)时,会有很多额外的烦恼,需要对共享资源.原子操作等概念深入理解,需要理解涉及的内核有关数据结构,这些数据结构对理解文件.共享有重要作用: 最后介绍dup.fcntl.sync.fsync和ioctl函数. 二.文件描述符 open或creat文件时,内核--文件描述符fd-->

Unix环境高级编程学习笔记(四):进程环境

1 exit函数与_Exit函数 #include<stdlib.h> void exit(int status) void _Exit(int status) 这两个函数的不同之处在于exit函数先执行清理工作后再进入内核(清理I/O缓冲),_Exit函数直接进入内核 2 atexit函数,登记函数,在exit的时候执行 int atexit(void (* func) (void)); 被登记的函数称为终止处理函数,这些函数的调用顺序与登记顺序相反,如果一个函数被登记多次,也会被调用多次

Unix环境高级编程学习笔记(七):线程

1 线程包含线程ID,一组寄存器的值,栈,调度优先级和策略,信号屏蔽字,errno变量,以及线程私有数据.进程的所有信息对于该进程的所有线程都是共享的,包括可执行程序文本,程序全局内存和堆内存,栈以及文件描述符. 线程可以通过pthread_self函数获得自身线程ID #include<pthread.h> pthread_t pthread_self(void) 新增进程可以通过pthread_create函数创建 #include <pthread.h> int pthrea

Unix环境高级编程学习笔记(三):标准I/O , 系统数据文件和信息

1 标准I/O函数不同于read,write函数,是其在流上进行操作, 当首次调用标准I/O函数时,系统会首先调用malloc,为流创造缓冲区, 2 fopen函数 #include<stdio.h> file * fopen(const char* pathname, const char * restrict name); 打开返回指针,出错返回NULL, type的取指有r(读),w(写),a(追加),r+/w+(读+写),a+(读+写+追加) int fclose(file* fp)

UNIX环境高级编程(阅读笔记)---多线程信号

多线程信号 1.默认情况下,信号将由主进程接收处理,就算信号处理函数是由子线程注册的 2. 每个线程均有自己的信号屏蔽字,可以使用sigprocmask函数来屏蔽某个线程对该信号的响应处理,仅留下需要处理该信号的线程来处理指定的信号. 3. 对某个信号处理函数,以程序执行时最后一次注册的处理函数为准,即在所有的线程里,同一个信号在任何线程里对该信号的处理一定相同 4. 可以使用pthread_kill对指定的线程发送信号 5. 在主进程中对sigmask进行设置后,主进程创建出来的线程将继承主进