0%

什么是进程时间

进程时间包含:

  1. CPU 在用户模式下消耗的时间。
  2. CPU 在内核模式下消耗的时间(比如 I/O、页错误处理)。

times 系统调用

#include <sys/times.h>

clock_t times(struct tms *buf);

其中 tms 结构体包含了当前进程和等待的子进程的时间信息。“等待的子进程”含义是:只有调用 wait 或者 waitpid 回收了一个子进程,子进程的时间才会累加到当前进程的 tms_cutimetms_cstime 上。

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 */
};

可以从哪些地方获取系统限制信息?

  1. limits.h 头文件中定义的限制。
  2. sysconf 获取的限制,这些限制在进程运行期间保持恒定。获取限制一般使用 _SC 开头的宏作为参数。
  3. pathconffpathconf 获取的限制,这些限制在进程运行期间是可以改变的。获取限制一般使用 _PC 开头的宏作为参数。

pid_t 类型的表示上限

在我的 wsl 中来看,pid_t 这个数据类型占用 4 个字节,是一个有符号整数(实际上是 int),我这里 /proc/sys/kernel/pid_max 显示可用的最大 pid 号为 4194304,即 $2^{22}$。这其实和 PID_MAX_LIMIT 相等,不可能将 pid_max 调整得更大,但是可以小于这个值。在 32 位机器上,这个系统限制最大是 32768,即 $2^{15}$。最大可用的 pid 值比文件中的值小 1,见后文。

参考 https://unix.stackexchange.com/a/162105/

$ sudo sysctl -w kernel.pid_max=4194303  # 改小可以
kernel.pid_max = 4194303
$ sudo sysctl -w kernel.pid_max=4194305  # 改大不行
sysctl: setting key "kernel.pid_max": Invalid argument
$ sudo sysctl -w kernel.pid_max=4194304  # 改回默认值
kernel.pid_max = 4194304

直接向 /proc/sys/kernel/pid_max 里面 echo 也是可以的,只是 sudo 给命令的提权并不会作用于当前 shell 的重定向符号(重定向使用的权限是当前的 shell 的)。这种情况下可以用 ... | sudo tee,也可以用 sudo bash -c '...'echo 和重定向包在内部。

在 Linux/UNIX 系统编程手册中看到这样的一段代码:

(py310) xxx /data/apue $ file /proc/self/fd/0
/proc/self/fd/0: symbolic link to /dev/pts/2
(py310) xxx /data/apue $ file /proc/self/fd/0 < .
/proc/self/fd/0: symbolic link to /data/apue

这说明目录也是能够被重定向并使用 0 号 fd 打开的,只要程序支持标准输入流不是普通文件就行。

/proc 下的进程信息

这些信息都是以 /proc/PID 开头的,其中 PID 为进程自己的进程号。用 /proc/self 可以表示进程本身。

在内核 2.4 之后,Linux 增加了线程组概念,正式支持了 POSIX 线程模型。/proc/PID/task/TID 是进程号为 PID 的进程中线程号为 TID 的线程的信息文件夹。

由于 $$ 表示当前 shell 的进程号,而一个进程中的主线程号和进程号相同,我们可以用以下方式来访问当前 shell 主线程的文件夹:

(py310) xxx /data/apue $ ls /proc/$$/task/$$
arch_status  cgroup      cmdline  cwd      fd       io        maps       mounts  oom_adj        pagemap      root       sessionid  smaps_rollup  statm    uid_map
attr         children    comm     environ  fdinfo   limits    mem        net     oom_score      personality  sched      setgroups  stack         status   wchan
auxv         clear_refs  cpuset   exe      gid_map  loginuid  mountinfo  ns      oom_score_adj  projid_map   schedstat  smaps      stat          syscall

C 语言标准库的缓冲

C 语言输入输出函数会将数据缓冲到用户区域,从而减少了系统调用次数

可以用函数 setvbuf 来改变一个 FILE * 的缓冲方式:

int setvbuf(FILE *restrict stream, char buf[restrict .size],
           int mode, size_t size);

其中 mode 有三种:

  • _IONBF: unbuffered
  • _IOLBF: line buffered
  • _IOFBF: fully buffered

Linux 进程凭证

进程凭证包括以下内容:

  • 实际用户 ID(real user ID)和实际组 ID(real group ID)。
  • 有效用户 ID(effective user ID)和有效组 ID(effective group ID)。
  • 保存的 set-user-ID(saved set-user-ID)和保存的 set-group-ID(saved set-group-ID)。
  • 文件系统用户 ID(file-system user ID)和文件系统组 ID(file-system group ID)(Linux 专有)。
  • 辅助组 ID。

保存的设置用户/组 ID

文件系统中每个非目录文件有设置用户/组 ID 位,启动这样的文件会使得有效用户/组 ID 按照文件的所有者来设置。出于安全考虑,在 Linux 中可执行文件的设置用户/组 ID 权限对于 shell 脚本无效

设计保存的设置用户 ID(保存的设置组 ID 同,为了表述方便略去)是为了让程序的有效用户 ID 能够在实际用户 ID保存的设置用户 ID 之间切换。这样程序在不需要使用其他用户(尤其是 root)的权限时可以将有效用户 ID 转回实际用户 ID,需要权限时再转回来。程序启动时,保存的设置用户 ID 从有效用户 ID 复制过来。

文件系统用户/组 ID

文件系统用户/组 ID 管辖和文件系统操作相关的权限问题,是由 Linux 专有的。一般情况下它们和有效用户/组 ID 相同,除非调用了 setfsuid / setfsgid

在 Linux 上推荐使用的创建临时文件的方法只有 mkstemptmpfile。前者是系统调用,用起来更复杂一点,后者是 C 标准库函数。其他的函数多多少少有自己的问题。

tmpfile 不再需要我们提供字符数组来存储文件名,返回的是 FILE* 而不是 fd。此外还在刚创建文件之后就使用了 unlink 来删除文件,这样就只有当前进程可以访问到这个文件了(除非 fork)。

#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    // 创建文件,记得关闭。
    FILE *fp;
    assert((fp = tmpfile()) != NULL);

    // 查询文件路径并打印。
    char path[128];
    char link_path[128];
    int fd = fileno(fp);
    assert(fd != -1);    
    sprintf(link_path, "/proc/self/fd/%d", fd);
    assert(readlink(link_path, path, sizeof(path)) != -1);
    printf("tmpfile: %s\n", path);

    fprintf(fp, "write something\n");
    fflush(fp);

    // 在这里停顿。由于已经 unlink 了这个文件,文件系统上看不到它。
    getchar();

    fclose(fp);
}

运行结果:

(py310) xxx /data/apue $ /data/apue/build/apue
tmpfile: /tmp/#943520 (deleted)

/proc/self/cmdline 是一个以 \0 结尾的文件,我尝试过用 fgets 读取,但是失败了;转而使用 fgetc 逐字符读文件。

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    FILE *f = fopen("/proc/self/cmdline", "r");
    if (!f) {
        perror("fopen");
        return 1;
    }
    
    int i = 0, j = 0;
    int ch;
    char buf[128];
    while ((ch = fgetc(f)) != EOF) {
        if (ch == '\0') {
            buf[j] = '\0';
            printf("argv[%d]: %s\n", i++, buf);
            j = 0;
        } else {
            buf[j++] = ch;
        }
    }

    fclose(f);
}

例子:

(py310) xxx /data/apue $ /data/apue/build/apue 5 265
argv[0]: /data/apue/build/apue
argv[1]: 5
argv[2]: 265