读书笔记-APUE第三版-(8)进程控制
进程ID
每一个进程都有一个唯一的进程ID。几个特殊进程:
- 0号进程是内核进程,一般是调度进程swapper。
- 1号进程init,是用户进程(以root权限执行/sbin/init),负责初始化。
- 几个重要函数:getpid(进程ID)/getppid(父进程ID)/getuid(进程真有用户ID)/geteuid(进程有效用户ID)/getgid(进程真有用户组ID)/getegid(进程有效用户组ID)。
fork/exec/wait例程
fork家族函数用于创建子进程(父子进程关系下节详细介绍),子进程往往调用exec家族函数运行新程序(fork+exec操作在有些系统中被称为spawn孵化),而wait家族函数用于获取子进程终止状态。
system函数使用/bin/sh运行命令,下面是使用fork/exec/wait实现的简单版本号
#include<sys/wait.h> #include<errno.h> #include<unistd.h> int system(constchar *cmdstring) /* version without signal handling */ { pid_t pid; int status; if (cmdstring == NULL) return(1); /* always a command processor withUNIX */ if ((pid = fork()) < 0) { status = -1; } else if (pid == 0) { /* fork返回值为0,表示是在子进程中*/ execl("/bin/sh", "sh","-c", cmdstring, (char *)0); _exit(127); /* execl error */ } else { /* 在父进程中,fork返回子进程pid */ while (waitpid(pid, &status, 0) < 0) { if (errno != EINTR) { status = -1; /* error other than EINTR fromwaitpid() */ break; } } } return(status); }
- fork函数一次调用,在父进程和自己成中两次返回,由于父进程能够fork多个子进程,所以设计成在父进程中返回子进程pid,而在子进程返回0,由于子进程能够通过getpid和getppid获取自身pid和父进程pid。常见应用场景是在网络编程中,父进程while循环监听用户请求,当接收到用户请求,fork出子进程进行处理。注:linux系统中,fork通过clone系统调用实现。
- waipid函数使用參数0等待指定子进程返回,wait函数家族包含wait(等待任一子进程返回)/waitpid(等待指定子进程&组返回,并能通过第三个參数设置堵塞选项)/waittid(进一步扩展,能获取导致进程终止的信号信息等)/wait3(还能够返回进程使用的资源)/wait4(其它wait函数的入口)。
- exec家族函数的作用是替换掉当前进程上下文(text/data/heap/stack等),运行新的程序(不会创建新进程)。exec家族函数包含execl/execlp/execle/execv/execvp/execve,各个函数主要差别在參数上,当中l表示是列表形式,v表示是指针数组形式,e表示环境变量,p表示命令參数是相对路径,会在PATH路径中搜索。
父子进程
子进程和父进程共享仅仅读的text段,针对bss段、对、栈,现代操作系统使用COW(copy-on-write)技术,仅仅有发生改变时,才会拷贝对应的内存页。
父子进程关系
子进程会继承父进程的大量属性,当中一些重要属性包含:真实/有效用户信息,进程组/会话信息,工作文件夹,环境变量,资源限制等。
父子进程最明显的差别是:子进程的tms时间统计信息被清零,子进程不会继承文件锁,未决闹钟&信号等(兴许章节讨论)。
子进程和父进程返回先后顺序是不确定的,假设用户程序对父子进程运行顺序有依赖,须要自行处理,比方使用信号实现等待通知机制等。
- 内核为每一个正在终止的进程保留了少量信息(pid,终止状态,CPU时间等),便于父进程获取其终止状态。
- 假设子进程在父进程之前结束,而父进程没有wait,子进程会变成僵尸进程。
- 假设父进程先结束,子进程的父进程会变成init进程(pid为1),所以假设要避免僵尸进程的产生,能够两次调用fork,即在子进程中再次调用fork,然后退出。这样第二个fork出来的进程因为其父进程退出,所以被init进程接管。
文件共享
子进程会dup父进程打开的文件描写叙述符(共享文件描写叙述符close-on-exec标记),包含标准输出、输入和错误输出。
如图,父子进程共享file tableentry,位置偏移量一致,所以要父子进程读写同一文件时要注意同步。
设置进程用户ID
之前提到,子进程会继承父进程的uid和euid(有效用户ID),能够调用setuid(setgid)改动进程用户(组)。
- 假设是root用户调用,会同一时候改动进程的uid、euid和备份euid(saved set-user-id)。登陆后,由login(root进程)设置用户ID。
- 非root用户仅仅能改动euid,并且仅仅能改动成之前的uid或者备份euid,否则出错。
- 假设自进程运行exec方法,并且运行程序的set-user-ID位被设置,那么euid被设置被运行程序属主ID。
- 备份euid复制euid。正常情况下,uid=euid=备份euid。
- 程序编写遵循“最小权限“模型,当且仅当程序须要高权限时,才调用setuid提升权限,操作完之后再调用setuid恢复权限。
其它进程相关函数
- 进程审计:acct开启进程审计功能,系统记录已终止进程的统计信息,包含用户ID,启动时间,CPU时间等。Linux系统审计记录保存在/var/log/account/pacct,须要用fread读取acct结构体信息。
- 进程调度:进程调整nice值来设置执行优先级(你越nice,你的优先级越低,人艰不拆。。),相关函数:nice/getpriority/setpriority
- 进程时间:调用clock_t times(structtms *buf )函数,当中返回值为时钟时间,而tms结构体被下面内容填充:
struct tms { clock_t tms_utime; /* user CPU time */ clock_t tms_stime; /* system CPU time */ clock_t tms_cutime; /* user CPU time, terminated children */ clock_t tms_cstime; /* system CPU time, terminated children */ };
注意:相关时间已经通过每秒滴答数(_SC_CLK_TCK)转化成了秒数,但它是从过去任一时间開始统计的,所以其绝对值无意义。一般分别在进程開始和结束调用times,再计算之间的时间差。