pag171
ID-----0 通常为调度进程,常常被称为交换进程,
不执行任何磁盘上的程序,也被称为系统进程。
ID-----1 通常为init进程,通常读与系统有关的初始化文件,并将系统引导到一个状态,init进程绝不会终止。(普通用户进程(与交换进程不同,不是内核中的系统进程),但以超级用户特权运行)
pid_t getpid(void);//进程ID
pid_t getppid(void);//父进程ID
uid_t getuid(void);//进程实际用户ID
uid_t geteuid(void);//进程有效用户ID
gid_t getgid(void);//进程实际组ID
gid_t getegid(void);//进程有效组ID
pag172
pid_t fork(void)
子进程返回0 (一个进程只有一个父进程,总可以调用getppid获得父进程ID)
父进程返回新子进程ID(可能有多个子进程,且没有一个函数使一个进程获得其所有子进程的进程ID)
子进程时父进程的副本:
子进程获得父进程的数据空间,堆和栈的副本。父子进程并
不共享这些
存储空间部分,他们
共享正文段(非存储空间)。
写时拷贝:
并不执行父进程数据段,栈和堆的完全拷贝,这些区域由父子进程共享,内核将他们的访问权限改变为只读。当父子进程中任何一个试图改变这些区域时,内核只为修改区域的那块内存制作一个副本。
strlen:包含终止null字节
sizeof:不包含null字节
pag174
在重定向父进程的标准输出时,子进程的标准输出也被重定向。(fork:父进程所有打开文件描述符都被复制到子进程中)
子进程继承的属性:
- 实际用户ID、实际组ID、有效用户ID、有效组ID(进程和用户)(关系到权限)
- 附加组ID
- 进程组ID(进程和进程)(不关系到权限)
- 会话ID
- 控制终端
- 设置用户ID标识和设置组ID标识
- 当前工作目录
- 根目录
- 文件模式创建屏蔽字(umask)
- 信号屏蔽和安排
- 针对任一文件描述符的在执行时关闭(close on exec)标识
- 环境
- 连接的共享存储段
- 存储映射
- 资源限制
- 文件描述符
父子进程间区别:
- fork返回值
- 进程ID
- 父进程ID
- 子进程tms_*部分设置为0
- 父进程设置的文件锁不会被子进程继承
- 子进程的未处理闹钟被清除
- 子进程的未处理信号集设置为空集
vfork :不将父进程地址空间完全复制到子进程中,在子进程调用exec或exit之前,他在父进程的空间中运行。
vfork 保证子进程先运行,在子进程调用exec或exit之前,内核会使父进程处于休眠状态。
pag178
对于 父进程已经终止的所有进程,他们的父进程都改变 为init进程。
一个已经终止,但是其父进程尚未对其进行善后处理(获取终止进程的有关信息,释放他仍占用的资源)的进程称为 僵尸进程(调用wait、waitpid获取状态)
init只要有一个子进程终止就会调用一个wait函数取得终止状态,所以不会产生僵尸进程。
pag182
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);//WNOHANG可使调用者不阻塞
pid:
-----pid == -1-------等待任意子进程
-----pid > 0 -------等待其进程ID与pid相等的子进程
-----pid == 0--------等待其组ID与等于调用进程组ID的任一子进程
-----pid < -1 --------等待其组ID等于pid绝对值的任一子进程
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
idtype:
P_PID:等待特定进程
P_PGID:等待特定进程组中任一子进程
P_ALL:等待任一子进程
如果一个进程fork一个子进程,但不要他等待子进程终止,也不希望子进程处于僵死状态知道父进程终止,可以调用fork两次,先让父进程wait第一个子进程终止,使第二个子进程被init领养。(init子进程不会成为僵死进程)
pag188
int execl(const char *pathname, const char *arg0, ...);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ...);
int execve(const char *pathname, char *const argv[], char *const envp[]);//内核系统调用
int execlp(const char *filename, const char *arg0, ...);
int execvp(const char *filename, char *const argv[]);
exec函数:
进程调用此函数时,该进程执行的程序完全替换为新程序,而
新程序则从其main函数开始执行。
exec并不创建新进程,所以前后进程ID未改变。
exec函数用一个全新的程序
替换了当前进程的正文、数据、堆、栈段。
6个exec函数中字符寓意:
----p:该函数取filename作为参数,并且用PATH环境变量寻找可执行文件。
----l:该函数取一个参数表,他与字母v互斥//参数表以NULL指针结尾
----v:该函数取一个argv[]矢量。
----e:该函数取envp[]数组,而不使用当前环境
执行exec后保持的属性:
- 进程ID和父进程ID
- 实际用户ID和实际组ID
- 附加组ID
- 进程组ID
- 会话ID
- 控制终端
- 闹钟尚余留时间
- 当前工作目录
- 根文件
- 文件模式创建屏蔽子
- 文件锁
- 进程信号屏蔽字
- 未处理信号
- 资源限制
- tms_*
FD_CLOEXEC:进程中每个打开的描述符都有一个
执行时关闭标志。若此标志设置则在执行exec时关闭该描述符,否则该描述符打开。
pag193
int setuid(uid_t uid);//设置实际用户ID和有效用户ID
int setgid(gid_t gid);//设置实际组ID和有效组ID
关于内核维护的三个用户ID,注意:
- 只有超级用户进程可以更改实际用户ID。(setuid设置全部三个用户ID)
- 仅当对程序文件设置了设置用户ID位时,exec函数才会设置有效用户ID。任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置用户ID。
- 保存的设置用户ID是由复制有效用户ID而得来的。
int setreuid(uid_t ruid, uid_t euid);//交换实际用户ID和有效用户ID
int setregid(gid_t rgid, gid_t egid);//交换实际组ID和有效组ID
int seteuid(uid_t uid);
int setegid(gid_t gid); //pag196设置不同用户ID的各函数
pag200
int system(const char *cmdstring);
在其实现中调用了
fork、exec和waitpid。
如果一个进程正以特殊权限(设置用户ID或设置组ID)运行,他又想生成另一个进程执行另一个程序,则他应当
直接使用fork和exec,而且在fork之后,exec之前要改回到普通权限(exec会复制有效ID-》保存设置ID)。设置用户ID或设置组ID程序决不应调用system函数。
char * getlogin(void);//获取登录名
clock_t time(struct tms *buf);
不提供返回保存的设置用户ID(只是供setuid时做为一个参考,看要设置的有效id和保存的设置用户id相不相等)的函数,我们必须在进程开始时保存
有效用户ID。