8~11章主要讲进程相关的内容。
进程标示符(PID),0是调度进程,1通常是init进程,2一般是页守护进程(pagedaemon),负责虚拟内存的分页操作。
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void); //parent pid
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
区分实际ID,有效ID,保存的ID。
fork函数
pid_t fork();
子进程是父进程的副本,获得父进程空间的数据空间、stack、heap的副本,共享.text段。写时复制。
Linux:clone,fork的泛型,可以控制父子进程共享的部分;
父进程所有打开的文件描述符会被复制到子进程中,相同的描述符之间共享同一个文件表项;
父子进程之间有大量属性被继承,除了:进程ID、文件锁、闹钟、信号集。
fork后如果立刻执行新程序,也可以调用spawn;
废弃接口:vfork,建立子进程并阻塞父进程知道exit或exec被调用,父子进程之间共享数据段(子进程在父进程地址空间运行);
exit函数
_Exit(ISO)和_exit(Posix.1)在UNIX下同义,exit会执行全局的flush。
退出状态和终止状态:后者指的是进程结束状态,前者指的是传递给exit函数的参数或main的返回值;
如果父进程在子进程结束前终止,子进程将被init进程认领,其父进程自动转为init进程;
如果一个子进程已经结束,其父进程未做好善后处理,这样的进程就是所谓的僵尸进程(zombie,用ps显示为z);
wait和waitpid
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
wait的功能简单:等待任意子进程,阻塞父进程,等待到后返回子进程pid;
waitpid可以指定等待进程的pid(pid>0),或任意子进程(pid=-1),或指定组id(pid<-1),或要求组ID=调用进程组id;
options有些选项可以扩展功能,包括作业支持和非阻塞等待(WNOHANG,返回0)。
statloc如果非0,可以存放子进程终止状态,然后使用四个互斥的宏来解析这些状态,包括:
WIFEXITED:正常终止,可以通过WEXITSTATUS来获得终止返回值;
WIFSIGNALED:异常终止,可执行WTERMSIG取使之终止的信号编号;有些实现可以用WCOREDUMP取得核心转储文件;
WIFSTOPPED:若为当前暂停子进程返回的状态,则为真;可使用WSTOPSIG取使子进程暂停的信号编号;
WIFCONTINUED:若在作业控制暂停后已经继续的子进程返回了状态,则为真。
waitid //SUS
#include <sys/wait.h>
int waited(idtype_t idtype,id_t id, siginfo_t *infop, int options);
idtype可取:
P_PID:等待一个特定的进程;
P_PGID:等待一个特定进程组中的任一子进程;id包含要等待子进程的进程组ID;
P_ALL:等待任一子进程,忽略id;
options的参数与waitpid类似,包括作业控制、非阻塞等待等功能;
infop参数时指向siginfo的指针,该结构包含了有关引起子进程状态改变的生成信号的详细信息。
wait3和wait4
非posix,多一个参数,返回由终止进程及其所有子进程使用的资源汇总。
竞争条件
当多个进程以不确定的顺序处理共享数据,这就发生了竞争条件。避免的方法是使用轮询(耗时)或使用信号进行异步编程。
这里的例程用到了后面signal部分的知识。
exec函数
共6个exec系的函数,包括execl, execlp, execle, execv, execvp, execve,其中l表示list,p表示path,v表示vector,e表示environment,以p结尾的函数第一个参量都是filename,如果实参中不包括/,那么系统会在PATH中进行搜索该文件名;l和v是两种不同传递字符串参数的方法;有e的还有再传递环境变量(否则会继承父进程的);
参数表的长度有一个限制,如果超出长度,可以使用xargs来拆分参数;
exec系不会改变子进程的ID和一系列属性,对于文件的处理和FD_CLOEXEC标志有关,目录流一定会被关闭;在很多UNIX实现中,这6个函数只有execve是system call,其余都是library function
更改用户ID和组ID
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
su:real id,enable id和saved id都改为uid;
非su,uid=real id或saved id,此时设置enable id=uid;
非su,且不满足上述条件,errno=EPERM,返回-1;saved id需要_POSIX_SAVED_IDS为真;
如果没有setuid,exec系不会改变文件的enable id;saved id是exec函数复制enable id而来;
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
交换real id和enable id。
int seteuid(uid_t uid);
int setegid(gid_t gid);
更改enable id。
解释器文件
就是某些脚本文件,可以指定解释。
第一行的形式必须是#!pathname [optional-argument]
pathname指定解释器[绝对]路径,后面跟的是可选参数。一般有长度限制
解释器文件一般用来写其他(除了/bin/sh)之外的shell脚本文件。
system函数
标准库函数。如果实参传递NULL,则返回非0表示system函数可用;
在Unix中,system本质是fork+exec+waitpid;
如果fork失败,或waitpid返回除EINTER之外的出错,返回-1,errno改变;
如果exec失败,返回值同shell执行 exit(127);
否则所有3个函数都执行成功,返回shell的终止状态(即waitpid中的statloc);
system函数的优势:集成了各种错误处理和信号机制;
注意setuid或者setgid的程序绝不应该调用system函数,否则会引起安全方面的漏洞。
进程会计
某个Unix的选项功能,激活后可以让内核处理记录进程的某些统计数据,可以将之重定向到文件,进行日志记录。
用户标识
获得用户登录名:
char *getlogin(void);
然后就可以通过该函数的返回值调用getpwnam等函数取得其他所需信息。
进程时间
#include <sys/times.h>
clock_t times(struct tms *buf);
填充tms结构的元素包括用户cpu时间、系统cpu时间和已结束的子进程相关时间;
返回墙上时钟时间。