unix环境高级编程(进程环境)笔记

main函数

c程序从main函数开始

// argc: 参数个数
// 参数列表
int main(int argc, char *argv[]);

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

进程终止

有8种方式终止进程,5种为正常。
正常:

  • 从main返回;
  • 调用exit;
  • 调用_exit或_Exit;
  • 最后一个线程从其启动例程返回;
  • 从最后一个线程调用pthread_exit。

异常:

  • 调用abort;
  • 接到一个信号;
  • 最后一个线程对取消请求做出响应。

上面提及的main函数,在函数返回后立即调用exit函数,转为c代码可能是如下:
exit(main(argc, argv ));

  1. 退出函数

_exit和_Exit立即进入内核,exit则先执行一些清理处理,然后返回内核

#include <stdlib.h>
// 此函数会做标准I/O库的清理关闭操作:
// 对于所有打开流调用fclose函数,输出缓冲中的所有数据都被冲洗(写到文件上);
void exit(int status);
void _Exit(int status);

#include <unistd.h>
void _exit(int status);
  1. atexit函数
    一个进程可以登记多至32个函数,这些函数将由exit自动调用,称为终止处理程序,用atexit函数来登记这些函数:
#include <stdlib.h>
// exit调用这些函数的顺序与它们登记时候的顺序相反,同一函数如若登记多次,也会被调用多次。
int atexit(void (*func)(void));
										// 返回值:若成功,返回0;若出错,返回非0

exit首先调用各终止处理程序,然后关闭(通过fclose)所有打开流;如若程序调用exec函数族中的任一函数,则将清除所有已安装的终止处理程序。
在这里插入图片描述

环境表

每个程序都接收到一张环境表,环境表是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址,全局变量environ则包含了该指针数组的地址,称之为环境指针。
使用如下方式声明:

extern char **environ;

在这里插入图片描述
按照惯例,环境由name=value这样的字符串组成。历史上的unix系统的main函数带3个参数,第三个就是环境变量的字符指针数组,ISO C规定main函数只有两个,后来就变成了2个参数的main函数,通常也是使用getenv和putenv函数来访问特定的环境变量,而不使用environ变量,如果查看整个环境变量,则必须使用此变量。

C程序的存储空间布局

直接看图:
在这里插入图片描述

  • 正文段:这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是使用频繁执行的程序在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其指令;我理解这里就是代码段。
  • 初始化数据段:通常将此段称为数据段,它包含了程序中需明确地赋初值的变量,例如,C代码中声明的全局变量并且做了初始化赋值操作。
  • 未初始化数据段:通常将此段称为bss段(block started by symbol),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。C代码中的全局变量,但是未做初始化赋值。
  • 栈:局部变量,函数调用存放上下文信息的地方。
  • 堆:动态存储分配的部分。

共享库

共享库使得可执行文件中不再需要包含公用的库函数,而只需在所有进程都可引用的存储区中保存这种库例程的一个副本;程序第一次执行或者第一次调用某个库函数时,用动态链接方法将程序与共享库函数相链接;减少了每个可执行程序的长度,但增加了一些运行时间开销;这种运行时间开销在于程序第一次被执行或者说动态库函数第一次被调用时,共享库还可以使用新版本的库直接替换旧的版本库,进行功能升级,前提是动态库的被调用函数的原型不能改变。

存储空间分配

#include <stdlib.h>
// 分配指定字节数的存储区,此存储区中的初始值不确定;
void *malloc(size_t size);
// 为指定数量指定长度的对象分配存储空间,该空间中的每一位(bit)都初始化为0;
void *calloc(size_t nobj, size_t size);
// 增加或减少以前分配区的长度,当增加长度时,可能需要将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值则不确定。
// 若realloc的参数ptr为空指针,则realloc的功能与malloc相同。
void *realloc(void *ptr, size_t newsize);
										// 3个函数返回值:若成功,返回非空指针;若出错,返回NULL
void free(void *ptr);

这三个函数分配的空间都是进行了适当对齐的,
realloc可以对以前分配的内存进行增减,如果在增加的时候,在原内存区域后面有足够的空间进行扩充,就载原来的内存区的位置上向高增长,无需移动原数据,如果不够,就会重新开辟内存空间,拷贝数据,释放原来的内存。因为有数据的移动,不能使有任何指针指向原内存区。
分配空间的函数一般使用sbrk系统调用实现,释放的空间可以被之后再申请,但一般都保存在malloc的池中,不直接返回给内核。大多数分配的空间都比申请的大,因为要用于记录管理信息,以便再free的时候知道释放多少。所以不要对申请的空间进行越界访问。

环境变量

前面说得,环境变量是:name=value 的形式。

#include <stdlib.h>
char *getenv(const char *name);
										// 返回值:指向与name关联的value的指针;若未找到,返回NULL
			
// putenv取形式为name=value的字符串,将其放到环境表中,如果name已经存在,则先删除其原来的定义					
int putenv(char *ptr);
										// 返回值:若成功,返回0;若出错,返回非0
// setenv将name设置为value,如果在环境中name已经存在,那么:
// (a)若rewrite非0,则首先删除其现有的定义;
// (b)若rewrite为0,则不删除其现有定义(name不设置为新的value,而且也不出错);
int setenv(const char *name, const char *value, int rewrite);
// unsetenv删除name的定义,即使不存在这种定义也不算出错。
int unsetenv(const char *name);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1

还有一个clearenv函数。可以查找资料了解函数。
简单记录一下原理:环境表和环境字符串通常直接存放在进程存储空间顶部,栈之上,删除一个环境变量的时候,只要把后面的字符指针往前面移动就行了,增加就比较复杂,因为默认的环境变量表,环境字符串等都是在栈之上的,向上和向下都是不可以进行扩展了,所以:

  1. 如果修改一个现有得name:
    a). 新的value长度小于等于旧的长度,直接覆盖就行了。
    b). 新的大于旧的长度,则需要使用malloc开辟新的字符串空间,然后复制到该空间,再使环境变量中的对应指针指向此空间。
  2. 如果要增加name,首先malloc出name=value的字符串空间,然后将值复制到该空间:
    a). 如果是第一次增加name,则需要调用malloc开辟新的指针表空间,将原来的表信息拷贝到新的表空间中,新增加的name=value放在新表空间的末尾,表最后是一个NULL指针,最后使environ指针指向新的表。
    b). 如果不是第一次,则使用realloc增加一个指针空间,将此指针空间指向新的name=value。

函数setjmp和longjmp

c语言中有goto可以进行函数内的跳转,而longjmp可以在函数之间跳转。

#include <setjmp.h>

// 不是从longjmp过来的,返回0 是的话,返回longjmp的第二个参数的值
int setjmp(jmp_buf env);
										// 返回值:若直接调用,返回0;若从longjmp返回,则为非0
// 跳转到env保存的栈的样子中,第二个参数是setjmp的返回值
void longjmp(jmp_buf env, int val);

函数的调用是会形成函数栈的,先使用setjmp保存当前的栈信息,保存在jmp_buf中,然后再后面的调用的函数中,使用longjmp跳转到setjmp函数的位置。理解这个过程需要了解函数调用时栈中的样子。
局部变量和寄存器变量在开启编译优化的时候,返回到setjmp的值的时候会回到原来的值,全局变量,静态变量,volatile变量则会保存为后面函数修改后的值。

函数getrlimit和setrlimit

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

#include <sys/resource.h>

struct rlimit {
    rlim_t rlim_cur;  /* Soft limit */
    rlim_t rlim_max;  /* Hard limit (ceiling for rlim_cur) */
};


int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
										// 两个函数返回值:若成功,返回0;若出错,返回非0

软限制是内核为相应资源强制执行的值。硬限制是软限制的上限:无特权的进程只能将其软限制设置为介于0到硬限制之间的值,并(不可逆地)降低其硬限制。
值RLIM_INFINITY表示对资源没有限制
第一个参数值如下所示:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值