5.1 进程标志
每个进程都有一个pid唯一的表示它,虽然是唯一的,但是是可以复用的。
ID为0的进程通常是调度进程,由于不执行任何磁盘上的程序,所以又是系统进程。
ID为1的进程通常是init进程,在自举过程结束时由内核调用。
ID为2的是页守护进程,此进程负责支持虚拟存储器系统的分页操作。
pid_t getpid()
获取调用进程的pid
pid_t getppid()
获取调用进程的父进程pid
uid_t getuid()
获取调用进程的用户id
gid_t getgid()
获取调用进程的组用户id
5.2 创建进程
可以使用一些函数创建一个进程
子进程和父进程会继续执行fork之后的命令,子进程获得父进程数据空间,堆栈的副本,但是不是共享的,父子进程共享的只有正文段
子进程还会复制父进程中标准io缓冲区中未清楚的部分
注意由于文件部分的实现,文件描述符指向的文件表项是由内核管理,所以父子进程是会指向同样的文件表项的,除非修改文件描述符,文件表项中包含了文件的偏移量(即任意进程修改过后下一个进程读到的就是修改的内容)!
网络服务进程中一般会各自关闭它们不需要的文件描述符,以免产生不必要的影响。
现在的很多实现并不执行一个父进程数据段,堆栈的完全副本,使用了写时复制的技术。
这些区域由父子进程共享,内核将它们的访问权限改变为只读。如果父进程或者子进程中的任意一个试图修改这些区域,则内核只为修改区域那一块内存制作一块副本,通常是虚拟内存中的一页。
事实上,内核也为子进程分配了一段页表,但是这些页表指向父进程,并且标记为只读,当子进程试图写的时候会发生写保护的错误,错误处理方式就是复制出错的内容进入页表。
pid_t fork()
两种不同的返回值
子进程返回0
父进程返回创建的子进程的pid
vfork不同关于fork,一般它是为了调用exec才创建的,所以
vfork中:
在父进程的空间中运行,不会像fork一样执行写时复制!其实就是关闭了页表的只读变成可写。
一旦子进程修改数据(除了vfork的返回值),进行函数调用,或者没有exec或者exit就返回都可能带来问题
保证子进程先运行,一旦子进程中调用了exec或者exit之后才会调度父进程恢复运行。
注意exit()函数和_exit()函数的区别,exit()函数会冲洗缓冲区,所以父进程的缓冲区很可能会被清洗。
pid_t vfork()
5.3 僵尸进程和孤儿进程
孤儿进程:
对于父进程已经终结的进程,它们的父进程都会改变为init进程
操作过程为: 当一个进程终结时,内核会逐个检查所有活动进程,以判断它是否是正要终结的进程的子进程,如果是,则将其的父进程id 改为1
注意,孤儿进程并不会变为僵尸进程,因为它终结的时候init进程必定会调用一个wait函数取得其终止状态。
僵尸进程:
终止进程的父进程调用wait或者waitpid时,可以得到一些子进程的信息,包括进程id,终止状态和CPU时间总量。内核会根据这些信息释放终止进程的存储区,关闭其所有打开的文件。
一个已经终止,但是父进程尚未对其进行善后处理的进程称为僵尸进程。
//老子不收尸,儿子变僵尸
//孤儿不用怕,养父是老大
//生个孙子当龙子,哈哈哈
//简单的例子,不需要等待孙子进程,孙子进程也不会变成僵尸进程
if(fork())
{
//进程1处理进程2结束
wait(NULL);
}
else
{
//进程2fork出进程3再终结
if(fork())
{
exit();
}
else
{
//成功获得养父进程init
}
}
5.4 进程结束处理
当一个进程正常或异常终止的时候,内核就会向其父进程发送SIGCHLD信号,这是个异步事件,可能在父进程运行的任何时候发生。
//返回终结的子进程id
pid_t wait(int *statloc)
如果子进程已经终止并且是个僵尸进程,则立即返回并且取得该子进程的状态
否则wait阻塞直到任意一个子进程终结,由于返回子进程的id,所以总会知道是哪个子进程终结了
statloc:
NULL 不关心终止状态
整形指针 将终止状态填入地址指向的内存中
pid_t waitpid(pid_t pid,int *statloc,int options)
pid:
-1 等待任意子进程
>0 等待进程id与pid相等的子进程终结
0 等待组id等于调用进程组id的任一子进程
<-1 等待组id等于pid绝对值的任一子进程
statloc同上
options:
0 阻塞等待
或者下面的按或的结果
WCONTINUE
WNOHANG 不阻塞直接返回0(如果没有子进程为僵尸进程的话)
WUNTRACED
注意,除了父进程可以等待子进程的结束,子进程也可以等待父进程的结束
//子进程等待父进程的结束
while(getppid()!=1)
{
//非阻塞
}
while(getppid()!=1); //阻塞方式
5.5 函数exec
当进程调用一种exec的时候,该进程执行的程序完全替换成为新程序,而新程序则从其main函数开始执行,因为调用exec并不创建新进程,前后的进程id未改变,exec只是用磁盘上的一个新进程替换了当前进程的正文段,数据段,堆段和栈段。
用exec可以初始执行新的程序,exit函数和wait函数处理终止和等待终止。
exec
//exec(l,v)(p)
//l表示将参数传入到函数参数中
//v表示将参数定义为一个传入到函数的参数中
//p代表需要自定义环境变量,若没有p则继承进程的环境变量
int system(const char *cmdstring)
//会调用fork,返回该进程的终止状态
相当于在命令行中直接输入cmdstring
5.6 更改用户id和更改用户组id
注意需要有超级权限
int setuid(uid_t uid) //设置用户id
int setgid(gid_t gid) //设置用户组id