UNIX 环境编程 Note ( UPDATING )
和数值相关的一些定义:
CHAR_BIT, CHAR_MAX, CHAR_MIN, SCHAR_MAX, SCHAR_MIN, UCHAR_MAX, INT_MAX, INT_MIN, UINT_MAX, SHRT_MAX, SHRT_MIN, USHRT_MAX, LONG_MAX, LONG_MIN, ULONG_MAX, LLONG_MAX, LLONG_MIN, ULLONG_MAX, MB_LEN_MAX, // 在一个多字节字符常量中的最大字节数 FOPEN_MAX, // 允许同时打开的文件数 TMP_MAX, FILENAME_MAX, // 文件名长度
和系统运行相关的一些定义:
ARG_MAX -- exec 函数族的参数最大长度 ATEXIT_MAX -- 可用 atexit 函数登记的最大函数个数 CHILD_MAX -- 每个实际用户 ID 子进程最大个数 DELAYTIMER_MAX -- 定时器最大超限运行次数 HOST_NAME_MAX -- gethostname 返回的主机名长度 LOGIN_NAME_MAX -- 登录名最大长度 OPEN_MAX -- 赋予新建文件描述符的最大值 + 1 PAGESIZE -- 系统内存页大小(单位: 字节) RTSIG_MAX -- 为应用程序预留的事实信号的最大个数 SEM_NSEMS_MAX -- 一个进程可使用的信号量最大个数 SEM_VALUE_MAX -- 信号量最大值 SIGQUEUE_MAX -- 一个进程可排队信号的最大个数 STREAM_MAX -- 一个进程一词可打开的标准 I/O 流的最大个数 SYMLOOP_MAX -- 路径解析过程中可访问的符号链接数 TIMER_MAX -- 一个进程的定时器最大个数 TTY_NAME_MAX -- 终端设备名长度,其中包括 null 字节 TZNAME_MAX -- 时区的字节数
linux 系统中存在三种基础的配置, sysconf
, pathconf
和 fpathconf
。 这些函数可获取系统中在各个维度对进程做出的限制。通过 man 手册可以轻松的查阅。
POSIX.1 variables We give the name of the variable, the name of the sysconf() argument used to inquire about its value, and a short description. First, the POSIX.1 compatible values. ARG_MAX - _SC_ARG_MAX The maximum length of the arguments to the exec(3) family of functions. Must not be less than _POSIX_ARG_MAX (4096). CHILD_MAX - _SC_CHILD_MAX The maximum number of simultaneous processes per user ID. Must not be less than _POSIX_CHILD_MAX (25). HOST_NAME_MAX - _SC_HOST_NAME_MAX Maximum length of a hostname, not including the terminating null byte, as returned by gethostname(2). Must not be less than _POSIX_HOST_NAME_MAX (255). LOGIN_NAME_MAX - _SC_LOGIN_NAME_MAX Maximum length of a login name, including the terminating null byte. Must not be less than _POSIX_LOGIN_NAME_MAX (9). NGROUPS_MAX - _SC_NGROUPS_MAX Maximum number of supplementary group IDs. clock ticks - _SC_CLK_TCK The number of clock ticks per second. The corresponding variable is obsolete. It was of course called CLK_TCK. (Note: the macro CLOCKS_PER_SEC does not give information: it must equal 1000000.) OPEN_MAX - _SC_OPEN_MAX The maximum number of files that a process can have open at any time. Must not be less than _POSIX_OPEN_MAX (20). PAGESIZE - _SC_PAGESIZE Size of a page in bytes. Must not be less than 1. PAGE_SIZE - _SC_PAGE_SIZE A synonym for PAGESIZE/_SC_PAGESIZE. (Both PAGESIZE and PAGE_SIZE are specified in POSIX.) RE_DUP_MAX - _SC_RE_DUP_MAX The number of repeated occurrences of a BRE permitted by regexec(3) and regcomp(3). Must not be less than _POSIX2_RE_DUP_MAX (255). STREAM_MAX - _SC_STREAM_MAX The maximum number of streams that a process can have open at any time. If defined, it has the same value as the standard C macro FOPEN_MAX. Must not be less than _POSIX_STREAM_MAX (8). SYMLOOP_MAX - _SC_SYMLOOP_MAX The maximum number of symbolic links seen in a pathname before resolution returns ELOOP. Must not be less than _POSIX_SYMLOOP_MAX (8). TTY_NAME_MAX - _SC_TTY_NAME_MAX The maximum length of terminal device name, including the terminating null byte. Must not be less than _POSIX_TTY_NAME_MAX (9). TZNAME_MAX - _SC_TZNAME_MAX The maximum number of bytes in a timezone name. Must not be less than _POSIX_TZNAME_MAX (6). _POSIX_VERSION - _SC_VERSION indicates the year and month the POSIX.1 standard was approved in the format YYYYMML; the value 199009L indicates the Sept. 1990 revision. POSIX.2 variables Next, the POSIX.2 values, giving limits for utilities. BC_BASE_MAX - _SC_BC_BASE_MAX indicates the maximum obase value accepted by the bc(1) utility. BC_DIM_MAX - _SC_BC_DIM_MAX indicates the maximum value of elements permitted in an array by bc(1). BC_SCALE_MAX - _SC_BC_SCALE_MAX indicates the maximum scale value allowed by bc(1). BC_STRING_MAX - _SC_BC_STRING_MAX indicates the maximum length of a string accepted by bc(1). COLL_WEIGHTS_MAX - _SC_COLL_WEIGHTS_MAX indicates the maximum numbers of weights that can be assigned to an entry of the LC_COLLATE order keyword in the locale definition file. EXPR_NEST_MAX - _SC_EXPR_NEST_MAX is the maximum number of expressions which can be nested within parentheses by expr(1). LINE_MAX - _SC_LINE_MAX The maximum length of a utility's input line, either from standard input or from a file. This includes space for a trailing newline. RE_DUP_MAX - _SC_RE_DUP_MAX The maximum number of repeated occurrences of a regular expression when the interval notation \{m,n\} is used. POSIX2_VERSION - _SC_2_VERSION indicates the version of the POSIX.2 standard in the format of YYYYMML. POSIX2_C_DEV - _SC_2_C_DEV indicates whether the POSIX.2 C language development facilities are supported. POSIX2_FORT_DEV - _SC_2_FORT_DEV indicates whether the POSIX.2 FORTRAN development utilities are supported. POSIX2_FORT_RUN - _SC_2_FORT_RUN indicates whether the POSIX.2 FORTRAN run-time utilities are supported. _POSIX2_LOCALEDEF - _SC_2_LOCALEDEF indicates whether the POSIX.2 creation of locales via localedef(1) is supported. POSIX2_SW_DEV - _SC_2_SW_DEV indicates whether the POSIX.2 software development utilities option is supported. These values also exist, but may not be standard. - _SC_PHYS_PAGES The number of pages of physical memory. Note that it is possible for the product of this value and the value of _SC_PAGESIZE to overflow. - _SC_AVPHYS_PAGES The number of currently available pages of physical memory. - _SC_NPROCESSORS_CONF The number of processors configured. See also get_nprocs_conf(3). - _SC_NPROCESSORS_ONLN The number of processors currently online (available). See also get_nprocs_conf(3).
clock_t comp_t dev_t fd_set fpos_t gid_t ino_t mode_t nlink_t off_t pid_t pthread_t ptrdiff_t rlim_t sig_atomic_t sigset_t size_t ssize_t time_t uid_t wchar_t
进程由内核 exec 调用,而进程的入口函数一般为 main 函数,但是也不一定,编译器一般在编译链接的时候有一个默认链接脚本,这个脚本给的入口函数就是 main 函数。
而启动进程所使用的参数和环境变量是从内核中取得的。
一般退出函数有以下几个:
// stdlib.h -- ISO void exit(int status); void _Exit(int status); // unistd.h-- POSIX.1 void _exit(int status);
如果想在进程推出的时候统一处理一些事情,可以使用 atexit
注册退出函数。 这些函数会在执行 exit 函数的时候执行。 先注册的函数会后执行,而且该功能并不会有去重处理,如果一个函数调用多次,那么这个函数也会执行多次。 一个进程至少可以注册 32 个终止处理函数,不同系统提供的数量不一样。 而且在所有的终止处理函数调用之后才会关闭所有打开的流。 注意,调用 _exit
和 _Exit
可能并不会触发终止处理函数。
// stdlib.h int atexit( void (*func)(void) );
以下是一些小知识点:
- 如果 main 默认整形返回值,而最后一行推出的语句不是 return 语句,那么将采用隐式返回,默认值为 0。 如果其他情况不指定返回值,则返回结果无定义。
- 在 main 函数中调用 return 和调用 exit 是等价的。
每一个程序穷之后都会默认拥有一个全局变量 const char **environ
, 该变量指向一组环境变量字符串,其中格式为 env_name=value
。 该字符串指针数组的最后一个值为 NULL
, 因此你可以根据该特性判断字符串数组否遍历完。
操作环境变量有以下函数: 另外还可以通过 char *getenv(const char *name);
和 int putenv(char *string);
来访问特定的环境变量。
// stdlib.h int setenv(const char *name, const char *value, int overwrite); int unsetenv(const char *name); int putenv(char *string); char *getenv(const char *name); char *secure_getenv(const char *name); int clearenv(void);
C 程序一般由以下几个部分组成:
- 正文段: CPU 机器指令部分
- 初始化数据段:包含已经初始化的变量,包含初始化值
- 为初始化数据段: BSS
- 栈:
- 堆
一般可以通过 size
指令查看可执行文件的分配。
// chrome 浏览器的例子 size ~/bin/chrome.115.0.5790.102/opt/google/chrome/chrome text data bss dec hex filename 212567609 9669428 2722712 224959749 d689d05 /xxx/bin/chrome.115.0.5790.102/opt/google/chrome/chrome
另外在内存空间中可能还会存在其他类型的段,比如:包含符号表的段,包含调试信息的段以及包含动态共享库链接表的段 ...
ISO 中定义了以下几种从堆空间分配内存空间的方法:
// stdlib.h void *malloc(size_t size); void *calloc(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size); void *reallocarray(void *ptr, size_t nmemb, size_t size); void free(void *ptr);
注意,realloc 后地址指针可能发生变化,因此,最好不要将 realloc 出来的地址空间的地址赋予其他指针,因为在 realloc 之后可能造成内存越界访问等未知错误。
在使用 xalloc 分配的空间一般是调用 sbrk
分配出来的,因为要管理这些从内核分配出来的存储区域,因此会在分配的内存区域存在一些管理数据。 如果在使用分配区域时发生越界写,可能会造成一些不可预知的问题(比如 SegmentFault, 或者对象的属性值被篡改等 ),但是因为这些错误一般不会马上显示出来,因此很难追查。 所以使用的时候需要格外小心。
为了方便检查内存泄露,越界访问,重复释放等问题,下面有些更加安全的库可以参考:
1. libmalloc 该库除了提供标准的一些内存分配回收函数外还提供了,用于存储空间配置的mallopt
, 和用于统计信息的mallinfo
2. vamalloc 3. quick-fit 分配速度跟快,但是管理结构占用空间更大。 4. jemalloc BSD 8.0 中使用的一个工具。 5. TCMalloc google 开源, Google perftools 中的一个工具。 6. 函数 alloca 从当前函数栈上分配内存,在函数退出时释放。 但是某些平台因为进入函数之后无法变更栈的长度导致该函数不可用,因此在使用前应该先测试一下。
通用的一些环境变量:
变量 | 描述 |
---|---|
COLUMNS | 终端宽度 |
DATEMSK | gatedate 模板文件路径名 |
HOME | home 起始目录 |
LANG | 本地名 |
LC_ALL | 本地名 |
LC_COLLATE | 本地排序名 |
LC_TYPE | 本地字符分类名 |
LC_MESSAGES | 本地消息名 |
LC_MONETARY | 本地货币编辑名 |
LC_TIME | 本地日期,时间格式名 |
LINES | 终端高度 |
LOGNAME | 登录名 |
MSGVERB | fmtmsg 处理消息组成部分 |
NLSPATH | 消息类模板序列 |
PATH | 搜索可执行文件路径名前缀 |
PWD | 当前工作目录的绝对路径 |
SHELL | 用户首选 shell 名 |
TERM | 终端类型 |
TMPDIR | 创建临时文件的路径名 |
TZ | 时区信息 |
setjump
和 longjump
goto 语句的局限性在于只能在一个函数内调用,而如果涉及到要跨函数进行 goto 的时候就要用到 setjump
和 longjump
函数了。
// setjmp.h int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val);
getrlimit
和 setrlimit
该两个函数用于操作进程的一些限制。函数定义如下所示:
// sys/resource.h int getrlimit(int resource, struct rlimit *rlp); int setrlimit(int resource, const struct rlimit *rlp);
具体平台有哪些资源可以更改可以 man getrlimit
查看一下:
在使用该函数的时候需要注意以下三点:
- 设置的 softlimit 一定要小于等于 hardlimit
- 设置的 hardlimit 只能设置得比之前小
- 普通用户 hardlimit 只能设置得更小,不能设置到更大,只有 root 用户才能设置到更大
一般可以操作的限制有如下这些:
变量 | 描述 |
---|---|
RLIMIT_AS | 总进程的最大可用存储空间数,这影响到 sbark 函数和 mmap 函数 |
RLIMIT_CORE | core 文件的最大字节数,如果是 0 则代表禁止创建 core 文件 |
RLIMIT_CPU | 最大的 CPU 使用时间(统计),如果超过这个 softlimit 进程会受到 SIGXCPU 信号 |
RLIMIT_DATA | 数据段最大字节长度 |
RLIMIT_FSIZE | 可以创建文件的最大字节长度 |
RLIMIT_MEMLOCK | 一个进程用 mlock 可以锁定的最大长度 |
RLIMIT_MSGQUEUE | 消息队列可分配的最大限制 |
RLIMIT_NICE | 进程设置 NICE 值的最大限度 |
RLIMIT_NOFILE | 每个进程可打开的最多文件数 |
RLIMIT_NPROC | 每个实际用户 ID 可拥有的子进程数量 |
RLIMIT_NPTS | 用户可同时打开的伪终端数量 |
RLIMIT_RSS | 最大驻内存集字节长度 |
RLIMIT_SBSIZE | 在任意时刻,一个用户可以占用的套接字缓冲区最大长度限制 |
RLIMIT_SIGPENDING | 一个进程可排队的信号最大数量 |
RLIMIT_STACK | 栈的最大字节长度 |
RLIMIT_SWAP | 用户可消耗的交换空间的最大字节数 |
RLIMIT_VMEM | 和 RLIMIT_AS 相同 |
进程相关函数有下面这些:
// 获取进程的标识符有以下这些函数 // unistd.h pid_t getpid(void); pid_t getppid(void); uid_t getuid(void); -- 获取调用该进程的用户 ID uid_t geteuid(void); gid_t getgid(void); gid_t getegid(void); char *getlogin(void); -- 获取登录名 // unistd.h // sys/types.h extern char **environ; int execl(const char *pathname, const char *arg, ... /* (char *) NULL */); int execlp(const char *file, const char *arg, ... /* (char *) NULL */); int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */); int execv(const char *pathname, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]); int fexecve(int fd, char *const argv[], char *const envp[]); pid_t fork(void); pid_t vfork(void); -- vfork 被该书认定为是有缺陷的,不应该在可移植的程序中包含他 void exit(int status); int nice(int incr); -- 调整进程优先级,普通用户只能调低优先级,特权用户才能调高优先级,进程只能影响自己的优先级 int getpriority(int which, id_t who); -- which 可以取三个值 ---- PRIO_PROCESS 表示进程 ---- PRIO_PGRP 表示进程组 ---- PRIO_USER 表示用户 ID int setpriority(int which, id_t who, int value); // time.h time_t time(time_t *_Nullable tloc); -- 获取 Unix 时间戳 // sys/time.h -- RETURN -- The return value may overflow the possible range of type clock_t. -- On error, (clock_t) -1 is returned, and errno is set to indicate the error. clock_t times(struct tms *buf); struct tms { clock_t tms_utime; /* user time */ clock_t tms_stime; /* system time */ clock_t tms_cutime; /* user time of children */ clock_t tms_cstime; /* system time of children */ };
进程一般都有一个非负整数的 ID, ID 是唯一的。 一般 0 号进程为内核调度进程,常被叫做交换进程 swapper
, 该进程是内核一部分,并不执行任何磁盘上的程序,因此也称为系统进程。 ID 1 的进程一般为 init 进程。
现有的进程可以通过 fork
函数创建一个进程。 由 fork 创建的进程称为子进程, fork 函数会返回两次,区别是,子进程的返回值为 0, 而负进程会返回子进程的进程 id.
// unistd.h pid fork(void);
子进程继承父进程属性有以下这些:
- 打开的文件
- 实际用户 ID,实际组 ID,有效用户 ID,有效组 ID
- 附属组 ID
- 进程组 ID
- 会话 ID
- 控制终端
- 设置用户 ID 标志和设置组 ID 标志
- 当前工作目录
- 根目录
- 文件模式创建屏蔽字
- 信号屏蔽和安排
- 对任意打开文件描述符的执行时关闭(close-on-exec)
- 环境
- 连接的共享存储段
- 存储映像
- 资源限制
父进程和子进程的具体区别如下:
- Fork 的返回值不通
- 进程 ID 不通
- 两个进程的父进程 ID 不通
- 子进程的 tms_utime, tms_stime, tms_cuttime, tms_ustime 的值设置为 0
- 子进程不继承父进程设置的文件锁
- 子进程的未处理闹钟被清除
- 子进程的未处理信号集设置为空集
vfork 创建子进程时并不会将父进程的地址空间完全复制到子进程中,因为 vfork 被认定为马上会调用 exec 或者 exit,于是也不会用到原有的地址空间。 这种优化的工作方式在某些 unix 系统中实现以提高工作效率,单如果子进程修改数据,进行函数调用,或者没有调用 exec 或者 exit 就返回会带来未知的结果(一般是不好的结果)。
Vfork 会保证子进程先运行,在他调用 exec 或者 exit 之后父进程才可能被调用运行。因此如果子进程中有依赖父进程的资源,那么会导致死锁。
Vfork 中如果修改了父进程的项目会直接影响原有父进程的值。
- 如果你想要让你的程序以静态的方式编译,那么可以给 gcc 增加 --static 属性。比如
gcc --static -o helloworld helloworld.c
- 《Unix 环境高级编程》
![]() |
![]() |