第7章——《进程环境》

### 实验环境介绍
* gcc:4.8.5
* glibc:glibc-2.17-222.el7.x86_64
* os:Centos7.4
* kernel:3.10.0-693.21.1.el7.x86_64

引言

  • 这章讨论内容
    • main函数是如何被调用的
    • 命令行参数如何传递给新程序。
    • 典型的存储空间布局
    • 如何分配另外的存储空间
    • 进程如何使用环境变量
    • 进程的不同终止方式。
    • longjmp和setjmp函数以及它们与栈的交互作用
    • 进程资源限制

main函数

  • 当内核执行C程序的时(使用一个exec函数),在调用main先调用一个特殊的启动例程。可执行程序文件将次启动例程指定为程序的起始地址——这是由链接编辑器设置的。而链接编辑器由C编译器调用。启动例程从内核取得命令行参数和环境变量值。为上述调用main做好安排

进程终止的8中方式

  • 正常终止
    • 从main返回
    • 调用exit
    • 调用_exit或_Exit
    • 最后一个线程从其启动例程返回
    • 从最后一个线程调用pthread_exit
  • 异常终止
    • 调用abort
    • 接收到一个信号
    • 最后一个线程对取消请求做出响应
退出函数

退出函数
* _exit和_Exit立即进入内核,exit则先执行一些清理处理,然后返回内核。由于历史原因exit函数总是执行一个标准I/O库的清理关闭操作:对于所有打开流调用fclose函数。冲洗所有的输出缓冲区
* exit(0)等价于return 0

函数atexit

atexit
* 注册函数,在exit自动调用
* 测试代码如下:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define handle_error(msg) \
   do { perror(msg); exit(EXIT_FAILURE); } while (0)

void fun1(void);
void fun2(void);

int
main(int argc, char *argv[])
{
    if (atexit(fun1))
        handle_error("atexit");

    if (atexit(fun2))
        handle_error("atexit");

    exit(EXIT_SUCCESS);
}

void
fun1(void)
{
    printf("call exit_fun1\n");
}

void
fun2(void)
{
    printf("call exit_fun2\n");
}

result:
[manjingliu@localhost part_7]$ ./atexit 
call exit_fun2
call exit_fun1
  • 一个c程序是如何启动和终止
    程序启动和终止

命令行参数

  • 忽略

环境表

  • 每一个进程都会有有一张环境表
    环境表
  • 测试代码如下:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define handle_error(msg) \
   do { perror(msg); exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[], char *envp[])
{
    char **pp = envp;
    for (; (*pp) != NULL; pp++)
        printf("%s\n", *pp);

    exit(EXIT_SUCCESS);
}
result:
[manjingliu@localhost part_7]$ ./atexit 
XDG_SESSION_ID=368
HOSTNAME=localhost.localdomain
TERM=linux
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=192.168.137.1 50457 22
SSH_TTY=/dev/pts/1
USER=manjingliu
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/manjingliu
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/manjingliu/.local/bin:/home/manjingliu/bin
PWD=/home/manjingliu/apue/part_7
LANG=en_US.UTF-8
HISTCONTROL=ignoredups
SHLVL=1
HOME=/home/manjingliu
LOGNAME=manjingliu
SSH_CONNECTION=192.168.137.1 50457 192.168.137.142 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/1000
OLDPWD=/home/manjingliu/apue
_=./atexit

C程序的存储空间布局

程序存储空间安排
* C程序的组成
* 正文段:CPU执行的机器指令部分,通常只读
* 初始化数据段。一般叫做数据段,包含了程序汇总明确赋初值的变量。例如:int maxcount = 99;
* 未初始化数据段。通常叫做bss段。程序执行之前,内核将此段初始化为0或空指针。如:long sum[1000]
* 栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中
* 堆。用于动态存储分配

未初始化数据段的内容并不存放在磁盘程序文件中。内核在程序开始运行前将它们都设置为0.需要存放在程序文件中的只有正文段和初始化数据段

[manjingliu@localhost part_7]$ size atexit
   text data bss dec hex filename
   1301 548 4 1853 73d atexit

共享库

  • 不使用共享库
[manjingliu@localhost part_7]$ size a.out 
   text data bss dec hex filename
 770960 6196 8640 785796 bfd84 a.out
  • 使用共享库
[manjingliu@localhost part_7]$ size a.out 
   text data bss dec hex filename
   1301 548 4 1853 73d a.out

使用共享库和不使用共享库,正文和数据段的长度都显著减小

存储空间分配

存储空间分配

环境变量

环境变量
变量
* 测试代码

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define handle_error(msg) \
   do { perror(msg); exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[], char *envp[])
{
    char *ep = getenv("SHELL");
    printf("SHELL = %s\n", ep);
    exit(EXIT_SUCCESS);
}
result:
SHELL = /bin/bash

函数setjmp和longjmp

  • C语言中的goto语句是不能跨越函数的,而执行函数间的跳转是setjmp和longjmp。对于处理发生在很深层潜逃函数调用中的出错情况是很有用的
  • 在深层栈帧中处理出错,如果以返回值的方式进行返回检查,会变得麻烦
    非局部跳转
  • gcc优化对几种变量在函数间跳转的影响
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

static void f1(int, int, int, int);
static void f2(void);

static jmp_buf jmpbuffer;
static int globval;

int
main(void)
{
    int autoval;
    register int regival;
    volatile int volaval;
    static int statval;

    globval = 1; autoval = 2; regival = 3; volaval = 4; statval = 5;

    if (setjmp(jmpbuffer) != 0) {
        printf("after longjmp:\n");
        printf("globval = %d, autoval = %d, regival = %d,"
            " volaval = %d, statval = %d\n",
            globval, autoval, regival, volaval, statval);
        exit(0);
    }

    /*
     * Change variables after setjmp, but before longjmp.
     */
    globval = 95; autoval = 96; regival = 97; volaval = 98;
    statval = 99;

    f1(autoval, regival, volaval, statval); /* never returns */
    exit(0);
}

static void
f1(int i, int j, int k, int l)
{
    printf("in f1():\n");
    printf("globval = %d, autoval = %d, regival = %d,"
        " volaval = %d, statval = %d\n", globval, i, j, k, l);
    f2();
}

static void
f2(void)
{
    longjmp(jmpbuffer, 1);
}
result:
[manjingliu@localhost part_7]$ gcc -Wall setjmp.c -o setjmp 
[manjingliu@localhost part_7]$ ./setjmp 
in f1():
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
[manjingliu@localhost part_7]$ gcc -Wall setjmp.c -o setjmp -O
[manjingliu@localhost part_7]$ ./setjmp 
in f1():
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 2, regival = 3, volaval = 98, statval = 99

注意:全局变量、易失变量和静态变量不受优化的影响(改了就是改了)。进行优化后,auto变量和寄存器变量都是存放在寄存器中。所以如果要编写非局部跳转的程序,则必须使用volatile类型的,因为volatile是直接读内存,而不是读存储器,编译器为为了防止该变量被多线程经常改变,导致寄存器和内存的值不一致,所以会强制去读内存里的真实数据
自动变量的潜在问题
  • 如下代码:这里为这个io流设置行缓冲区,但是缓冲区为局部变量,会有安全隐患
FILE *
open_data(void)
{
    FILE *fp;
    char databuf[1024]; /* setvbuf makes this the stdio buffer */

    if ((fp = fopen("xxxx", "r")) == NULL)
        return NULL;

    if (setvbuf(fp, databuf, _IOLBF, 1024) != 0)
        return null;
    return fp;
}

getrlimit和setrlimit与进程资源限制

#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <errno.h> /* for definition of errno */
#include <stdarg.h> /* ISO C variable aruments */
#include <string.h>

#define MAXLINE 4096 /* max line length */

static void err_doit(int, int, const char *, va_list);

void
err_sys(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    exit(1);
}

#define doit(name) pr_limits(#name, name)

static void pr_limits(char *, int);

int
main(void)
{
#ifdef RLIMIT_AS
    doit(RLIMIT_AS);
#endif

    doit(RLIMIT_CORE);
    doit(RLIMIT_CPU);
    doit(RLIMIT_DATA);
    doit(RLIMIT_FSIZE);

#ifdef RLIMIT_MEMLOCK
    doit(RLIMIT_MEMLOCK);
#endif

#ifdef RLIMIT_MSGQUEUE
    doit(RLIMIT_MSGQUEUE);
#endif

#ifdef RLIMIT_NICE
    doit(RLIMIT_NICE);
#endif

    doit(RLIMIT_NOFILE);

#ifdef RLIMIT_NPROC
    doit(RLIMIT_NPROC);
#endif

#ifdef RLIMIT_NPTS
    doit(RLIMIT_NPTS);
#endif

#ifdef RLIMIT_RSS
    doit(RLIMIT_RSS);
#endif

#ifdef RLIMIT_SBSIZE
    doit(RLIMIT_SBSIZE);
#endif

#ifdef RLIMIT_SIGPENDING
    doit(RLIMIT_SIGPENDING);
#endif

    doit(RLIMIT_STACK);

#ifdef RLIMIT_SWAP
    doit(RLIMIT_SWAP);
#endif

#ifdef RLIMIT_VMEM
    doit(RLIMIT_VMEM);
#endif

    exit(0);
}

static void
pr_limits(char *name, int resource)
{
    struct rlimit limit;
    unsigned long long lim;

    if (getrlimit(resource, &limit) < 0)
        err_sys("getrlimit error for %s", name);
    printf("%-14s ", name);
    if (limit.rlim_cur == RLIM_INFINITY) {
        printf("(infinite) ");
    } else {
        lim = limit.rlim_cur;
        printf("%10lld ", lim);
    }
    if (limit.rlim_max == RLIM_INFINITY) {
        printf("(infinite)");
    } else {
        lim = limit.rlim_max;
        printf("%10lld", lim);
    }
    putchar((int)'\n');
}

static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
    char buf[MAXLINE];

    vsnprintf(buf, MAXLINE-1, fmt, ap);
    if (errnoflag)
        snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
          strerror(error));
    strcat(buf, "\n");
    fflush(stdout); /* in case stdout and stderr are the same */
    fputs(buf, stderr);
    fflush(NULL); /* flushes all stdio output streams */
}

result:
[manjingliu@localhost part_7]$ ./rlimt 
RLIMIT_AS (infinite) (infinite)
RLIMIT_CORE 0 (infinite)
RLIMIT_CPU (infinite) (infinite)
RLIMIT_DATA (infinite) (infinite)
RLIMIT_FSIZE (infinite) (infinite)
RLIMIT_MEMLOCK 65536 65536
RLIMIT_MSGQUEUE 819200 819200
RLIMIT_NICE 0 0
RLIMIT_NOFILE 1024 4096
RLIMIT_NPROC 3815 3815
RLIMIT_RSS (infinite) (infinite)
RLIMIT_SIGPENDING 3815 3815
RLIMIT_STACK 8388608 (infinite)

猜你喜欢

转载自blog.csdn.net/u012570105/article/details/81483080