• 《Linux/UNIX系统编程手册》第6章 进程


    关键词:getpid()、getppid()、environ、setjmp()、longjmp()等等。

    本章将研究进程结构,并重点关注进程虚拟内存的布局及内容。还会对进程某些属性进行考察。

    1. 进程和程序

     进程是一个可执行程序的实例。

    程序包含:

    • 二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。
    • 机器语言指令:对程序算法进行编码。
    • 程序入口地址:标识程序开始执行时的起始指令位置。
    • 数据:程序文件包含的变量初始值和程序使用的字面常量值。
    • 符号表及重定位表:描述程序中函数和变量的位置及名称。
    • 共享库和动态链接信息:程序文件所包含的一些字段,列出了陈旭运行时需要使用的共享库,以及加载共享库的动态链接器的路径名。
    • 其他信息:程序文件还包含许多其他信息,用以描述如何创建进程。

    从内核角度看,进程是由用户内存空间和一系列内核数据结构组成,其中用户空间包含程序代码及代码所使用的变量,而内核数据结构则用于维护进程状态信息。

    2. 进程号和父进程号

    获取进程号:

    #include <unistd.h>
    pid_t getpid(void);
        Always successfully returns process ID of caller

     查看当前系统支持的最大信号数,通过/proc/sys/kernel/pid_max一般为32768。

    int pid_max = PID_MAX_DEFAULT;
    
    #define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)

    查看进程父进程号:

    #include <unistd.h>
    pid_t getppid(void);
        Always successfully returns process ID of parent of caller

    3. 进程内存布局

    下图展示各内存段在x86-32体系结构中布局:

     

    每个进程所分配的内存有很多 部分祖晨,通常称之为段segment:

    • 文本段(text):包含了进程运行的程序机器语言指令。
    • 初始化数据段(data):包含了显式初始化的全局变量和静态变量。
    • 未初始化数据段(bss):包含了未进行显式初始化的全局变量和静态变量。
    • 栈(stack):一个动态增长和收缩的段,由栈帧(stack frames)组成。系统会为每个当前调用的函数分配一个栈帧。栈帧中存储了函数的局部变量、实参和返回值。
    • 堆(heap):可在运行时动态进行内存分配的一块区域。堆顶端称作program break。

     size可显示二进制可执行文件的文本段、初始化数据段、非初始化数据段的段大小。

    PS:修改代码对比maps,是否和内存结构吻合?和size是否吻合?目的是通过maps变化,确定内存来自于何处?

    4. 虚拟内存管理

    虚拟内存的规划之一是将每个程序使用的内存切割成小型的、固定大小的页单元。

    进程有效虚拟地址范围在其生命周期中变化可能会发生于如下场景:

    • 由于栈向下增长超出之前曾达到的位置。
    • 当在堆中分配或释放内存时,通过调用brk()、sbrk()、malloc()函数族来提升program break的位置。
    • 当调用shmat()连接System V共享内存区时,或者调用shmdt()脱离共享内存区时。
    • 当mmap()创建内存映射时,或者munmap()解除内存映射时。

    5. 栈和帧

    函数的调用和返回使栈的增长和收缩呈线性。每次调用函数时,会在栈上新分配一帧,每当函数返回时,再从栈上将此帧移去。

    内核栈是每个进程保留在内核内存中的内存区域,在执行系统调用的过程中供内核内部函数调用使用。

    每个用户栈帧包括如下信息:

    • 函数实参和局部变量:这些变量都是在调用函数时自动创建的,C中成为自动变量。函数返回时将自动销毁这些变量。这也是自动变量与静态全局变量主要的语义区别:后者与函数执行无关,且长期存在。
    • 函数调用的链接信息:每个函数都会用到一些CPU寄存器,会在被调用函数的栈帧中保存这些寄存器的副本。

     

    6. 命令行参数(argc, argv)

    每个C程序都必须有一个main()作为程序启动的起点。

    执行程序时,命令行参数通过两个入参提供给main()函数。

    第一个参数int argc,表示命令行参数的个数;第二个参数char *argv[]是一个指向命令行参数的指针数组,每参数都是以空字符结尾的字符串。

    通过/proc/PID/cmdline文件可以读取任一进程的命令行参数;GNU C可使用program_invocation_name和program_invocation_short_name找到程序名称。

    参数存储自己上限通过ARG_MAX限定,通过调用sysconf(__SC_ARG_MAC)确定上限值。

    程序使用getopt()库函数解析命令行选项。

    7. 环境列表

    环境变量字符串都以名称=值形式定义。新进程在创建之时,会继承其父进程的环境副本。

    可以通过export key=value来设置环境变量,也可以通过set key=value设置,通过unset key来撤销环境变量。

    可以通过/proc/PID/environ检查任意进程的环境列表。

    7.1 查看环境变量

    C中可以通过char **environ访问环境列表。

    通过指针遍历environ变量:

    #include <stdio.h>
    #include <stdlib.h>
    
    extern char **environ;
                    /* Or define _GNU_SOURCE to get it from <unistd.h> */
    
    int
    main(int argc, char *argv[])
    {
        char **ep;
    
        for (ep = environ; *ep != 0; ep++)
            puts(*ep);
    
        exit(0);
    }

    还可以通过int main(int argc, char *argv[], char *envp[])第三个参数访问环境列表。

    getenv()从进程环境中检索单个值:

    #include <stdlib.h>
    char *getenv(const char *name);
        Returns pointer to (value) string, or NULL if no such variable

    7.2 修改环境变量

    putenv()向调用进程的环境中添加一个新变量,或者修改一个已经村侧的变量值。

    #include <stdlib.h>
    int putenv(char *string);
        Returns 0 on success, or nonzero on error

    setenv()可以替代putenv()向环境中添加一个变量。如果name标识变量在环境中已经存在,且参数overrite值为0,则setenv()不改变环境。如果overwrite为非0,则setenv()函数总是改变环境。

    #include <stdlib.h>
    int setenv(const char *name, const char *value, int overwrite);
        Returns 0 on success, or –1 on error

    unsetenv()从环境中移除由name参数标识的变量。

    #include <stdlib.h>
    int unsetenv(const char *name);
        Returns 0 on success, or –1 on error

    clearenv()清除整个环境变量。

    #define _BSD_SOURCE /* Or: #define _SVID_SOURCE */
    #include <stdlib.h>
    int clearenv(void)
        Returns 0 on success, or a nonzero on error

    8. 执行非局部跳转:setjmp()和longjmp()

    使用库函数setjmp()和longjmp()可执行非局部跳转。非局部跳转是指跳转的目标为当前执行函数之外的某个位置。

    goto跳转只能在函数内部。

    #include <setjmp.h>
    int setjmp(jmp_buf env);
        Returns 0 on initial call, nonzero on return via longjmp()
    void longjmp(jmp_buf env, int val);

    setjmp()调用为后续由longjmp()调用执行的跳转确立了跳转目标。该目标正是程序发起setjmp()调用的位置。

  • 相关阅读:
    maven settings.xml 文件
    Ajax配合后端实现Excel的导出
    线性表的链式存储--单链表
    微软最新开源工具PowerToys,让你的win10更加好用!!
    二叉树合集(三):线索二叉树(图文详解)
    二叉树合集(二):霍夫曼树(图文详解)
    二叉树合集(一):二叉树基础(含四种遍历,图文详解)
    二叉树合集(六):高度平衡的二叉搜索树简介(图文解析)
    二叉树合集(五):二叉搜索树(图片详解,含基本操作)
    【LeetCode】108.将有序数组转换为平衡二叉树(中序遍历三种方法,java实现)
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/12272352.html
Copyright © 2020-2023  润新知