APUE第8章 进程控制
进程标识
进程标识pid是由一个唯一的非负整数表示的。
- ID为0的进程一般是调度进程,通常被称为交换进程(swapper),是内核的一部分
- 进程ID为1的一般是init进程,负责在自举内核后,启动一个UNIX系统。init是一个普通用户进程,但是它以超级用户特权运行。
- ID为2是页守护进程(page daemon),负责支持虚拟存储系统的分页操作。
以下列出一些相关函数:
1 |
|
fork函数
函数原型如下
1 |
|
- 创建子进程后,子进程是父进程的副本,他们拥有同样的数据空间和堆栈(注意不是共享),共享的只有程序的正文段
- 书中提到了sizeof和strlen作用于字符串的时候差别,一般而言sizeof会计算空字符串,比strlen多1。而如果是字符常量的话,sizeof一般在编译的时候就计算出了结果,strlen则在调用的时候才计算,所以sizeof有时候快一些。
- fork的子进程会复制父进程的文件描述符,所以当父进程的标准输入输出被重定向的时候,子进程的标准输入输出都会被重定向。
- 子进程不会继承父进程设置的文件锁,子进程的未处理闹钟会被清除,子进程的未处理信号集会被设置为空集
- fork+exec组合成一个操作称为spawn
fork失败的可能原因
- 系统中有太多的进程
- 该实际用户ID的进程总数超过了系统限制
fork的应用场景
- 父进程希望复制自己,然后父子进程执行不同代码段,在网络服务进程中很常见。
- shell进程,fork后exec命令
vfork函数
返回值与fork相同,不同之处如下:
- vfork也会创建新的进程,不过该进程的目的是exec一个新程序,所以vfork不会让子进程完全复制父进程的地址空间,在子进程调用exec或者exit之前,会在父进程的空间中运行,这时候如果子进程修改数据,就会影响到父进程。
- vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。当然如果子进程调用这两个函数之前依赖于父进程的进一步操作,会导致死锁。(复习下死锁的四个必要条件:互斥条件、占有且等待、不可抢占、循环条件)
exit函数
exit和_exit、_Exit的区别在于exit函数会调用终止处理程序(atexit注册的终止处理程序)和检查打开的文件并且调用用户空间的标准I/O清理程序(如fclose),而_exit和_Exit两个函数不进行这些处理,直接返回到内核处理。
atexit函数
atexit的声明如下
1 | int atexit(void (*func)(void)); |
传入的函数要求返回值和参数均为空,同一个函数被登记多次,也一样会执行多次,执行顺序和登记顺序相反。
正常终止情况
- main函数内调用return语句
- 调用exit函数
- 调用_exit或_Exit函数
- 进程的最后一个线程再其启动历程处返回时。此时返回值并不取决于线程返回值,而是0
- 进程的最后一个线程调用pthread_exit的时候,返回值同样不起作用,返回0
异常的终止情况
- 调用abort
- 进程接收到某些信号的时候
- 最后一个线程对“取消”请求作出响应。
最后不管如何终止,都会执行内核的一段代码,关闭所有打开的文件描述符,释放使用的存储器等。
父子进程结束时间不同带来的不同
- 当父进程比子进程先结束的时候,内核会检查所有进程,是否有进程是该进程的子进程,如果是的话,会将其的父进程id设为1,成为init收养的进程。
- 如果子进程先结束,内核会为子进程保存一定量的信息,所以父进程调用wait或waitpid的时候可以得到这些信息。
- 僵死进程:由于内核需要为父进程尚未处理的已结束子进程保留信息,所以这些进程叫做僵死进程。
另外,如果是父进程是init的话,子进程结束后,init就会调用wait,故不会变成僵死进程
wait和waitpid函数
对于子进程结束时内核发出的信号,父进程默认是忽略。但是如果调用wait或者waitpid则会发生如下的可能情况
- 如果其所有子进程都在运行,则阻塞
- 如果一个子进程已经终止,则获得其终止状态,然后返回
- 如果该进程没有任何子进程,则出错返回
两者区别
1 |
|
- waitpid函数中的pid参数有以下选项
- pid==-1的话,等待任意子进程,此时等同于wait
- pid>0的话,等待与pid相同的子进程,注意如果该进程没有这个pid的子进程的话,都可能出错。
- pid==0的话,等待组ID等于调用进程组ID的任意子进程
- pid<-1的话,等待组ID等于pid绝对值的任意子进程
- options参数(3个)可以指定对应的是否阻塞或对应作业控制等,详见APUE的P193
waitid
相比起waitpid,waitid会稍微更灵活一些
竞争条件
主要讲的是由于CPU的调度,父子间进程调度顺序和执行时间都不确定,如果有明确的顺序要求,就要使用IPC来使得进程间同步
exec
exec并不产成新的进程,而是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段
exec衍生出相应的7个执行函数,详见APUE的P199。
执行了exec命令后,基本上继承原来process上的大多数属性,但是如果文件描述符设置了FD_CLOEXEC(close-on-exec)的的话,就会关闭(当然默认关闭)。
用户ID和组ID的修改
实际用户是登录时就确定的(只有超级用户可以在进程执行的时候改变实际用户),有效用户则可在进程内执行的时候进行变化。
- 通过setuid(uid)修改有效用户,通过setgid(gid)来修改实际组id
- seteuid和setegid只修改有效用户和组
- 保存设置用户ID(SUID):是有效用户ID副本,既然有效用户ID是副本,那么它的作用肯定是为了以后恢复有效用户ID用的。
- 保存设置用户ID一般会在文件的位里面体现,即rws中的s
解释器文件
第一行是规定的格式
1 | ! 解释器所在位置 参数 |
如
1 | ! /bin/sh |
这样子能够避免机器先用shell读取文件,然后再调用fork、exec来调用对应的解释器。直接在第一行写明的话就可以直接用解释器调用了。
system
因为system实现是调用了fork、exec、waitpid,所以有三种返回值
- fork失败或者waitpid返回EINTR之外的错误,则返回-1
- exec失败(不能执行shell),则返回值为如同shell执行了exit(127)一样
- 否则3个函数都成功,返回最后的shell终止状态,格式同waitpid。
getlogin
1 |
|
获取登录的用户名
优先级相关内容
1 |
|
进程时间
1 |
|