《UNUX环境高级编程》(7)进程环境

news/2024/11/13 3:45:16/

1、引言

2、main函数

  • main函数的原型
    /*argc是命令行参数的数目,argv是指向各个指针所构成的数组*/
    int main(int argc,char *argv[]);
    

3、进程终止

  • 有八种方式使进程终止。其中5种是正常,它们是:

    1. 从main函数返回
    2. 调用exit
    3. 调用_exit或_Exit
    4. 最后一个线程从其启动例程返回
    5. 从最后一个线程调用pthread_exit
  • 异常终止有三种方式:

    1. 调用abort
    2. 收到一个信号
    3. 最后一个线程对取消请求做出响应

3.1、退出函数

  • 以下三个函数用于正常终止一个程序

    void _exit(int status); //系统调用,立即进入内核
    void _Exit(int status); //系统调用,立即进入内核
    void exit(int status); //先执行一些清理工作,然后返回内核。
    
    • exit函数总是执行标准I/O库的清理关闭操作,对于所有打开的流调用fclose函数,这会造成输出缓冲中的所有数据被写(冲洗)到文件上。
    • 这三个退出函数都有一个参数,即终止状态(或退出状态)。main函数返回一个整形值与用该值调用exit是等价的。于是在main函数中exit(0);等同于return 0;
    • 当调用这些函数时不带终止状态,或者main执行了一个无返回值的return语句,或者main没有声明返回类型为整形,则进程的终止状态是未定义的。
  • 实例:main执行了一个无返回值的return语句

    /*hello1.c*/
    #include        <stdio.h>int main()
    {printf("hello, world\n");return;//无返回值
    }
    

    命令行:

    lh@LH_LINUX:~/桌面/apue.3e/environ$ gcc -o hello hello1.c 
    hello1.c: In function ‘main’:
    hello1.c:6:2: warning: ‘return’ with no value, in function returning non-voidreturn;^
    lh@LH_LINUX:~/桌面/apue.3e/environ$ ./hello
    hello, world
    lh@LH_LINUX:~/桌面/apue.3e/environ$ echo $?
    13
    

    对程序进程编译然后运行,可见到其终止码是随机的。注意:$?指明上一次执行命令的返回值,同时回忆一下$#$*$?$0$1…的作用

3.2、atexit函数

  • 一个进程可以登记最多32个函数(一些操作系统实现可能更多)个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序,通过atexit函数来登记这些函数
    int atexit(void (*function)(void));
    
    • exit调用这些函数的顺序与登记它们的顺序相反,同一函数如果被登记多次也会被调用多次
    • exit函数会先调用各终止处理程序,再fclose所有打开流。然后再调用_exit函数终止进程。
    • 如果程序调用exec函数族,则会清除所有已经注册的终止处理程序。
      在这里插入图片描述
  • 内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是 显式或隐式(通过exit) 调用_exit_Exit。进程也可以非自愿的由一个信号终止。
  • 实例:使用atexit函数
    #include "apue.h"static void	my_exit1(void);
    static void	my_exit2(void);int
    main(void)
    {if (atexit(my_exit2) != 0)err_sys("can't register my_exit2");/*my_exit1登记了两次,则也会调用两次*/if (atexit(my_exit1) != 0)err_sys("can't register my_exit1");if (atexit(my_exit1) != 0)err_sys("can't register my_exit1");printf("main is done\n");return(0);//等价于exit(0);
    }static void
    my_exit1(void)
    {printf("first exit handler\n");
    }static void
    my_exit2(void)
    {printf("second exit handler\n");
    }
    
    命令行:
    root@LH_LINUX:/home/lh/桌面/apue.3e/environ# ./doatexit 
    main is done
    first exit handler
    first exit handler
    second exit handler
    
    可以看到exit调用这些函数的顺序与登记它们的顺序相反

4、命令行参数

  • 调用exec函数的进程可以将命令行参数传递给新程序。UNIX内核并不查看这些字符串,它们的解释完全取决于各个应用程序,因此需要通过exec将这些参数传递给进程

  • 实例:将所有命令行参数都回显到标准输出上。

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

    命令行:

    lh@LH_LINUX:~/桌面/apue.3e/environ$ ./echoarg arg1 arg2 arg3
    argv[0]: ./echoarg
    argv[1]: arg1
    argv[2]: arg2
    argv[3]: arg3
    lh@LH_LINUX:~/桌面/apue.3e/environ$ echo arg1 arg2 arg3
    arg1 arg2 arg3
    

    可以看到可执行文件echoarg将所有命令行参数都打印了出来,而echo程序不会回显第0个参数。注意:argv[argc]是一个空指针。

5、环境表

  • 每一个进程都有一张环境表,该表也是一个字符指针数组,其中每个指针指向一个以null结束的C字符串地址。全局变量environ包含了该指针数组的地址。
    extern char **environ;
    
    在这里插入图片描述
    • 每一个环境变量由name=value形式的字符串构成,其中name字段一般是大写字母组成。
    • 当然也可以通过main函数的第三个参数来访问环境变量(已弃用)
      int main(int argc, char* argv[], char**envp);//envp已弃用,规定应使用全局变量environ
      

6、C程序的存储空间布局

  • C程序由以下部分组成

    • 正文段(或代码段).text
      由CPU执行的机器指令部分组成(即程序编译之后,编译器会将代码翻译成二进制的机器码,机器码存储在代码段(.text)中)。通常正文段可以共享,所以即使是频繁执行的程序在存储器中也只需有一个副本。并且正文段通常是只读的,以防止程序由于意外而修改其指令。也有可能包含一些只读的常数变量,例如字符串常量等。
    • 初始化数据段.data
      通常将此段称为数据段。用于保存有非0初始值全局变量静态变量。(局部变量保存在栈中)
    • 未初始化数据段.bss
      用于保存没有初始值初值为0的全局变量和静态变量。在程序开始执行之前,内核将此段中的数据初始化为0和空指针。
    • 栈stack
      局部变量与每次函数调用时需要保存的信息存放在stack中。每次函数调用时,其返回地址以及调用者的环境信息(如某些寄存器的值)都存放入栈。然后,最近被调用的函数在栈上为其分配栈帧。递归的原理就是每次调用自身时,就用一个新的栈帧,因此一次函数调用实例中的变量不会影响到另一次函数调用实例中的变量。
    • 堆heap
      通常在堆中进行动态存储分配(malloc)。位于bss和stack中间。
      在这里插入图片描述
  • 可执行文件中还有一些其他类型的段:包含符号表的段;包含调试信息的段;包含动态库链接表的段等。这些部分并不装载到进程执行的程序映像中。

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

7、共享库

  • 共享库使得可执行文件中不再需要包含公用的库函数,而只需在所有进程都可引用的存储区中保存这种库例程的一个副本。
  • 程序第一次执行或者第一次调用某个库函数时,用动态链接方法将程序与共享库函数相链接。这减少了每个可执行文件的长度,但增加了一些运行时间开销。这种时间开销发生在该程序第一次被执行时,或者每个共享库函数第一次被调用时。共享库的另一个优点是可以用库函数的新版本代替老版本而无需对使用该库的程序重新编译(假如参数个数与类型都不变)。
  • 下面展示无共享库方式和使用共享库方式创建可执行文件。
    lh@LH_LINUX:~/桌面/apue.3e/environ$ gcc -static hello1.c
    hello1.c:3:1: warning: return type defaults to ‘int’ [-Wimplicit-int]main()^
    lh@LH_LINUX:~/桌面/apue.3e/environ$ ls -l a.out
    -rwxrwxr-x 1 lh lh 912728 77 20:54 a.out
    lh@LH_LINUX:~/桌面/apue.3e/environ$ size a.outtext	   data	    bss	    dec	    hex	filename824102	   7284	   6360	 837746	  cc872	a.out
    lh@LH_LINUX:~/桌面/apue.3e/environ$ gcc hello1.c
    hello1.c:3:1: warning: return type defaults to ‘int’ [-Wimplicit-int]main()^
    lh@LH_LINUX:~/桌面/apue.3e/environ$ ls -l a.out 
    -rwxrwxr-x 1 lh lh 8608 77 20:54 a.out
    lh@LH_LINUX:~/桌面/apue.3e/environ$ size a.out text	   data	    bss	    dec	    hex	filename1183	    552	      8	   1743	    6cf	a.out
    
    可以发现:使用共享库比不使用时,可执行文件的正文和数据段的长度都显著减少。注意:size()报告正文段、数据段和bss段的长度(以字节文单位),结果的第4列和第5列分别以十进制和十六进制表示的3段总长度。

8、存储空间分配

  • 以下三个函数用于存储空间动态分配(在堆上分配)

    void *malloc(size_t size); //分配指定字节数的存储区,此存储区中的初始值不确定
    void *calloc(size_t nmemb, size_t size); //为指定数量指定长度的对象分配存储空间。该空间中的每一位都初始化为0
    void *realloc(void *ptr, size_t size); //增加或减少以前分配区的长度。当增加长度时,可能需要将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区的初始值不确定。void free(void *ptr); //释放ptr指向的存储空间,被释放的空间通常被送入可用存储区池。之后,可在调用上述3个分配函数时再分配这些空间。
    • realloc函数使我们可以增减以前分配的存储区长度。比如我们在堆上有一个数组,想要扩充该数组的长度,并且在该存储区后有足够的空间可供扩充,则可以在原存储区位置上向高地址方向扩充,无需移动原先数组任何内容。如果在原存储区后没有足够空间,则realloc分配另一个足够大的存储区,将现有数组内容全部复制到新分配的存储区,然后释放原存储区,返回新存储区地址。如果ptr是NULL,则realloc与malloc函数功能相同。

    • 这些分配函数通常底层使用sbrk系统调用。该系统调用扩充或缩小进程的堆。

    • 虽然sbrk可以缩小堆区大小,但是大多数malloc和free的实现都不减少进程的存储空间,释放的空间可供以后再分配,将它们保存在malloc池中而不返回给内核

    • 大多数实现所分配的存储空间比所要求的要稍微大一些,额外的空间用来记录管理信息:分配块的长度、指向下一个分配块的指针等。这意味着如果超过一个已分配区的尾端或者在已分配区起始位置之前进行写操作,则会改写另一块的管理记录信息或其他动态分配对象,这种错误是灾难性的。

    • 致命错误:释放了一个已经释放了的块;调用free时使用的指针不是3个alloc函数的返回值等。

    • 若使用malloc函数在堆上动态分配内存空间但是忘记调用free函数,那么该进程占用的存储空间就会连续增加,这称为内存泄漏。如果不调用free释放不再使用的空间,那么进程地址空间长度会慢慢增加,直至不再有空闲空间。

  • 在栈上分配内存空间

    void *alloca(size_t size);
    
    • 它的调用方式与malloc相同,但是在当前函数的栈帧上分配存储空间而不是在堆中。
    • 优点:当函数返回时自动释放它所使用的栈帧,不用手动free释放
    • 缺点:增加了栈帧的长度,而某些系统的函数在已经被调用后不能增加栈帧长度,于是也不支持alloca函数。

http://www.ppmy.cn/news/768954.html

相关文章

ensp华为交换机基础命令

交换机基本操作命令 用户视图&#xff1a;基本操作&#xff0c;查看基本信息 系统视图&#xff1a;进阶操作&#xff0c;配置静态路由&#xff0c;动态路由 系统命令 system-view #用户视图模式切换至系统视图模式按q或者Ctrlz退出 display history-command #历史命令查询 …

华为防火墙基础操作

一、交代背景 做过一段时间的售后实施&#xff0c;发现防火墙设备上架真的很简单&#xff0c;但是对于没有入门的人来说也会有一定的不理解&#xff0c;初次上线我到底该做什么呢&#xff1f; 这个也是我当初开始做的时候的问题&#xff0c;当然了&#xff0c;初始都是利用web…

华为退出鸿蒙后,华为鸿蒙纯净模式怎么打开-华为鸿蒙纯净模式怎么关闭

随着华为鸿蒙系统的正式发布&#xff0c;很多朋友们都很关注鸿蒙的实际体验感受&#xff0c;小编为大家带来华为鸿蒙系统纯净模式设置的使用方法&#xff0c;功能实用非常出色&#xff0c;能够很好的阻挡误点和广告软件安装&#xff0c;提高了手机安全性和流畅感。 鸿蒙纯净模式…

华为服务器进入系统怎么退出安全模式,进入安全模式后怎样恢复系统

进入安全模式后怎样恢复系统 内容精选 换一换 DAS企业流程审批提供了设置高危操作审批流程的功能来帮助您保证数据安全。本章以对删库操作设置审批流程为例,指导您如何通过开通并使用DAS企业流程审批,设置高危操作审批流程,安全管控数据库高危操作,确保数据安全。准备需要开…

华为服务器进入系统怎么退出安全模式,服务器怎么进入安全模式

服务器怎么进入安全模式 内容精选 换一换 如果您需要使用鲲鹏GCC编译器中一些加速库&#xff0c;则需要先在服务端安装鲲鹏GCC编译器。鲲鹏GCC编译器是针对鲲鹏平台的高性能编译器&#xff0c;它基于开源GCC开发的编译器工具链(包含编译器、汇编器、链接器)&#xff0c;支持C、…

华为服务器进入系统怎么退出安全模式,服务器进入安全模式

服务器进入安全模式 内容精选 换一换 遇到该问题,请参考以下步骤排查解决。以集群模式下实例为例说明。如果在,执行2。如果不在,需要重新创建弹性云服务器实例,使之和文档数据库实例在同一个虚拟私有云下。如果有,检查安全组的配置规则是否满足要求,请参见设置安全组,然…

Rust 第二天---Rust基础总结

之前已经配置好了Rust的环境,那学习一门语言最开始就是去掌握了解它的基本语法.其实Rust的语法和大多编程语言没什么差别,熟悉C的应该很容易上手,所以今天就快速过一遍基础. 1. 变量与常量 变量应该是编程中最常用到的,但是Rust与其他语言不同的是在声明变量的时候必须说明这…

php分类信息网站源码完美运营版 含完整代码包和搭建教程

今天给大家分享一款php分类信息网站源码完美运营版&#xff0c;无任何bug&#xff0c;程序包内含完整代码包和搭建教程&#xff0c;可直接商业运营&#xff0c;源码采用PHPMysql组合开发。 系统页面截图&#xff1a; 系统主要功能一览&#xff1a; 1、程序支持手机端电脑端微信…