《APUE》第八章,之进程控制笔记。
1. 进程标识符
Unix/Linux中每个进程都有一个非负整数表示的唯一进程ID。ID唯一,但可以重用,大多数unix使用延迟重用算法,使得赋予新建进程的ID不同于最近终止进程所使用的ID。
系统中有一些专用进程。ID为0的进程是调度进程,常常被称为交换进程。该进程是内核的一部分。进程ID是1的通常是init进程,在自举过程结束时有内核调用,负责在自举内核后启动unix系统。init进程决不会终止,但它是一个普通的用户进程,以超级用户特权运行。init一个特殊的地方是,它会成为所有孤儿进程的父进程。某些unix系统,进程ID 2是页守护进程,负责支持虚拟存储系统的分页操作。
与进程标志符有关的系统调用主要有:
#inclue <unistd.h> 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 git_t getegid(void); 返回调用进程的有效组ID |
2. fork函数
fork函数创建一个新的进程。函数原型如下:
#include <unistd.h> pid_t fork(void); 返回值:子进程中返回0,父进程返回子进程ID,出错返回-1 |
fork函数被调用一次,但返回两次,两次返回的唯一区别是子进程的返回值是0,父进程的返回值是新子进程的ID。将子进程ID返回给父进程的理由是:一个进程的子进程可能有多个,而且没有一个函数是一个进程可以获得其所有子进程的ID。
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、队和栈的副本。注意这是子进程所拥有的副本。父、子进程并不共享这些存储空间,父子进程共享正文段。由于在fork之后进场跟随着exec,所以很多的实现并不执行一个父进程数据空间的完全复制,而是采用写时复制(copy-on-write,COW)策略。父子进程共享的部分的访问权限是只读的,如果父子进程中的任一个试图修改共享部分,则内核之为修改区域的那块内存制作一个副本。通常是存储器系统的一“页”。
实例:
#include <unistd.h>
#include <stdio.h>
int glob = 6;
char buf[] = "a write to stdout\n";
int main(int argc, char const *argv[])
{int var;pid_t pid;var = 88;printf("parent PID %d\n", getpid());printf("before fork:\n");if((pid = fork()) < 0) //create new child process{printf("fork error!\n");exit(0);}else if (pid == 0){/* child */glob++;var++;}else {/* parent */sleep(2);}printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);return 0;
}
运行结果如下:
$ ./fork
parent PID 3757
before fork:
pid = 3758, glob = 7, var = 89
pid = 3757, glob = 6, var = 88
一般来说,fork之后父进程先执行还是子进程先执行是不确定的,取决于内核的调度算法。如果要求父子进程之间同步,需要某种形式的进程同步通信,后面会讲到。
这里需要特别注意的是,fork与I/O函数之间交互关系,也就是fork前后的printf缓冲问题。注意fork之前的printf语句,printf是标准的输出函数,是带缓冲的。Unix中,如果标准输出连接到设备终端,则它是行缓冲的,否则他就是全缓冲的。换句话说,行缓冲就是遇到换行才会把缓冲区的内容打印到屏幕上;而全缓冲只有等到缓冲区满或者程序退出才把内容写到目的设备上。当我们直接运行该程序不进行输出重定向时,只得到printf输出的行内容一次,其原因是在fork之前,标准输出的缓冲区的内容已经由换行符冲洗。但是当标准输出重定向到一个文件时,却得到printf输出两次。其原因是,在fork之前调用了printf,但是执行fork时,该数据仍然在缓冲区(全缓冲),然后在将父进程的数据空间复制到子进程是,该缓冲区也被复制到子进程中,所以父子进程各自有了带有该行内容的缓冲区。因此printf的内容会被输出两次。
比如:我们使用 ./fork > a.out 运行上面的程序,所有fork之前printf的内容都会在a.out中出现两次,这就是由于全缓冲的原因。
再有我们将
printf("before fork:\n");
的换行符 \n 去掉,使用 ./fork 运行,则结果中该语句会输出两次。因为没有了换行符,这条语句的打印内容会被缓冲,然后缓冲区被复制到子进程,从而输出两次。