fork进程标识
id为0的是调度进程,被称为交换进程swapper,是内核的一部分
id为1的是init进程,是所以孤儿进程的父进程,不是内核一部分,只是个普通进程,但是拥有root权限,
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void);//实际用户 ID
uid_t geteuid(void);//有效用户 ID
gid_t getgid(void);
gid_t getegid(void);
fork
fork后,fd会被复制到子进程,好像执行了dup函数,父子进程相同文件的fd都共享一个文件表,dup的特性
每个进程表项中fd公用相同文件表
共享当前打开文件的偏移量
fork后处理fd有两种常见操作模式
- 父进程等待子进程文件,防止读写相互混合
- 各自分工,执行不同程序段
子进程不继承父进程设置的文件锁
fork失败的两个主要原因
- 系统中有太多进程
- 该实际用户的进程总数超过了系统限制,CHILD_MAX
fork的两种方法
- 父进程复制自己,父子进程执行不同代码段,比如网络服务进程,父进程等待客户端请求,当有请求时父进程fork使子进程处理请求,而父进程继续等待下一个服务请求
- fork后执行exec,也就是spawn,当然unix是fork和exec组合就是spawn
vfork
创建一个新进程,然后exec,如同上面2
不完全拷贝父进程的地址空间,vfork保证子进程先运行
exit
大多数unix系统中,exit是标准C库中的一个函数,_exit是系统调用
_exit 和 _Exit 同义,为进程提供一种无需运行终止处理程序或信号处理程序而终止的方法,unix中并不冲洗标准IO流
exit调用_exit
5种正常终止,3种异常终止
ps 退出状态和终止状态
父进程在子进程前终止,那么子进程会被init进程(进程id是1)收养
子进程终止了,但是父进程没有获取子进程的终止状态(调用wait或者waitpid),那么这些子进程称为zombie僵死进程
wait和waitpid
#include <sys/wait.h>
pid_t wait(int *statlog);
pid_t waitpid(pid_t pid,int *statlog,int options);
//statlog 是终止状态
调用wait
- 子进程还在运行,则阻塞
- 子进程已经终止,会理解返回终止状态
- 没有子进程会出错返回
waitid
#include <sys/wait.h>
int waitid(idtype_t idtype,id_t id,siginfo_t *infop,int options);
/*
idtype
P_PID
P_PGID
P_ALL
*/
类似waitpid,但是根据灵活
wait3,wait4
The wait4() call provides a more general interface for programs that need to wait for certain child processes, that need resource utilization statistics accumulated by child processes, or that require options. The other wait functions are implemented using wait4().
The waitpid() call is identical to wait4() with an rusage value of zero. The older wait3() call is the same as wait4() with a pid value of -1.
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
pid_t wait3(int *statlog,int options,struct rusage *rusage);
pid_t wait4(pid_t pid,int *statlog,int options,struct rusage *rusage);
竞争条件
第十章,信号机制
第十五章和第十七章,进程间通信 IPC
exec函数
fork后,进程调用一种exec函数时,会替换为新程序
7种exec
#include <unistd.h>
int execl(const char *pathname,const char *agg0,...);
int execv(const char *pathname,char *const argv[]);
//多了envp
int execle(const char *pathname,const char *arg0,...,char *const envp[]);
int execve(const char *pathname,char *const argv[],char *const envp[]);//系统调用
//pathname变filename
int execlp(const char *filename,const char *arg0,...);
int execvp(const char *filename,char *const argv[]);
//pathname变fd
int fexecve(int fd,char *const argv[],char *const envp);
FD_CLOEXEC,进程中每个打开的fd都有一个执行时关闭标志,也就是exec后fd是否关闭的控制
系统默认是在exec后仍然保持fd的打开
POSIX.1明确要求exec时关闭目录流,opendir实现,通过调用fcntl设置了关闭标志
很多unix实现中,execve是内核的系统调用,其余6个是库函数
更改用户id和组id
#include <unistd.h>
//更改实际用户id和组id 和 有效用户id和组id
//实际+有效
int setuid(uid_t uid);
int setgid(gid_t gid);
//更改有效用户id和组id
//有效
int seteuid(uid_t uid);
int setegid(gid_t gid);
//切换实际用户id和有效用户id
int setreuid(uid_t ruid,uid_t euid);
int setregid(gid_t rgid,gid_t egid);
谁可以更改用户id(实际用户id,有效用户id,保存设置用户id),下面三点也适用与组id
- 进程有超级用户特权,三者都设置为uid
- 不是root,但是uid等于用户id或者保存设置的用户id,setuid只将有效用户id设置为uid
- 1和2都不满足,那么返回-1
应用场景at程序
解释器文件
system
system的实现中调用了fork,exec,waitpid,有三种返回值
- fork失败,-1
- exec失败,127
进程会计
每当进程结束时内核就写一个会计记录
一个新进程创建时会初始化,进程结束会写一个会计记录,但是init进程和守护进程不会有会计记录
每个平台实现不一
用户标志
#include <unistd.h>
char *getlogin(void);
进程调度
nice值
#include <unistd.h>
int nice(int incr);
#include <sys/resource.h>
int getpriority(int which,id_t who);
/*
which:
PRIO_PROCESS
PRIO_PGRP
PRIO_USER
*/
int setpriority(int which,id_t who,int value);
进程时间
墙上时钟时间,用户cpu时间,系统cpu时间
#include <sys/time.h>
clock_t times(struct tms *buf);//返回墙上时钟时间
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 */
};