UNIX 环境编程 Note ( UPDATING )

知识点
limits.h 中提供的一些宏

和数值相关的一些定义:

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 -- 时区的字节数
sysconf 中提供的一些参数

linux 系统中存在三种基础的配置, sysconf, pathconffpathconf。 这些函数可获取系统中在各个维度对进程做出的限制。通过 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
7 进程环境
进程的启动

进程由内核 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 程序一般由以下几个部分组成:

  1. 正文段: CPU 机器指令部分
  2. 初始化数据段:包含已经初始化的变量,包含初始化值
  3. 为初始化数据段: BSS
  4. 栈:

一般可以通过 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 时区信息
函数 setjumplongjump

goto 语句的局限性在于只能在一个函数内调用,而如果涉及到要跨函数进行 goto 的时候就要用到 setjumplongjump 函数了。

// setjmp.h
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
函数 getrlimitsetrlimit

该两个函数用于操作进程的一些限制。函数定义如下所示:

// 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
  • 子进程不继承父进程设置的文件锁
  • 子进程的未处理闹钟被清除
  • 子进程的未处理信号集设置为空集
Fork 与 vfor 的差异

vfork 创建子进程时并不会将父进程的地址空间完全复制到子进程中,因为 vfork 被认定为马上会调用 exec 或者 exit,于是也不会用到原有的地址空间。 这种优化的工作方式在某些 unix 系统中实现以提高工作效率,单如果子进程修改数据,进行函数调用,或者没有调用 exec 或者 exit 就返回会带来未知的结果(一般是不好的结果)。

Vfork 会保证子进程先运行,在他调用 exec 或者 exit 之后父进程才可能被调用运行。因此如果子进程中有依赖父进程的资源,那么会导致死锁。

Vfork 中如果修改了父进程的项目会直接影响原有父进程的值。

tips
  • 如果你想要让你的程序以静态的方式编译,那么可以给 gcc 增加 --static 属性。比如 gcc --static -o helloworld helloworld.c
参考资料
  • 《Unix 环境高级编程》
原创文章,版权所有,转载请获得作者本人允许并注明出处
我是留白;我是留白;我是留白;(重要的事情说三遍)
posted @ 2023-07-31 01:07  Mojies  阅读(25)  评论(0编辑  收藏  举报