180 likes | 422 Views
1 fork: 创建新进程. 系统调用 fork 创建一个新进程,但新进程的指令段,用户数据段,用户堆栈段都是旧进程一模一样的复制。系统数据段也几乎全是旧进程的复制。 原先的进程被称作 “ 父进程 ” , 新创建的进程被称作 “ 子进程 ” 。在 UNIX 中, fork 系统调用是创建新进程的惟一方式。 fork 调用惟一能出错的原因是系统中的资源耗尽 。.
E N D
1 fork:创建新进程 系统调用fork创建一个新进程,但新进程的指令段,用户数据段,用户堆栈段都是旧进程一模一样的复制。系统数据段也几乎全是旧进程的复制。 原先的进程被称作“父进程”,新创建的进程被称作“子进程”。在UNIX中,fork系统调用是创建新进程的惟一方式。fork调用惟一能出错的原因是系统中的资源耗尽。
进程执行fork()系统调用,创建出一个新的进程。新的进程叫做子进程,原先的进程叫父进程。组成进程的四个要素中,子进程的指令段,数据段,用户堆栈段,统统都是父进程一模一样的复制。子进程的系统数据段也几乎都是父进程的复制,PCB中只有很少的几个成员父子不同,这至少包括进程标识号PID。这种子进程的初始状态复制父进程的方法,叫“继承”。从此以后,父子进程各自都有一套自己独立的数据和指令,以后的发展,父子进程可以有不同的行为,父子进程对数据的修改也互不影响。进程执行fork()系统调用,创建出一个新的进程。新的进程叫做子进程,原先的进程叫父进程。组成进程的四个要素中,子进程的指令段,数据段,用户堆栈段,统统都是父进程一模一样的复制。子进程的系统数据段也几乎都是父进程的复制,PCB中只有很少的几个成员父子不同,这至少包括进程标识号PID。这种子进程的初始状态复制父进程的方法,叫“继承”。从此以后,父子进程各自都有一套自己独立的数据和指令,以后的发展,父子进程可以有不同的行为,父子进程对数据的修改也互不影响。
【例7-6】使用fork创建子进程。 $ cat fork1.c int a; int main(int argc, char **argv) { int b; printf("[1] %s: BEGIN\n", argv[0]); a = 10; b = 20; printf("[2] a+b=%d\n", a + b); fork(); a += 100; b += 100; printf("[3] a+b=%d\n", a + b); printf("[4] %s: END\n", argv[0]); }
上述程序在执行时,进程执行到fork()系统调用,创建出一个新的进程。从执行a += 100开始,父子进程各有自己一套执行流在独立的执行。这条语句操作的是用户数据区中的变量a,父子进程操作的都是各自私有的数据,互不影响。 b += 100;访问用户堆栈区的数据b,同样的,父子进程互不影响。 printf需要在当前终端上打印,该库函数最终引用文件描述符1,文件描述符是进程系统数据段里的内容,在创建新进程的时候,子进程抄写的跟父进程一样。因此,子进程也是在当前的终端上打印,而不是输出到别的终端上。
$ ./fork1 [1] ./fork1: BEGIN [2] a+b=30 [3] a+b=230 [4] ./fork1: END [3] a+b=230 [4] ./fork1: END
这就是fork()创建新进程的方法。从概念上讲,子进程指令段,数据段,堆栈段都是父进程的复制。从逻辑上看,可以理解子进程和父进程有各自独立的私有存储空间,并且子进程的初始状态是父进程的复制。了解了内核处理的这些技术,就不用担心这样做会占用很多内存。这就是fork()创建新进程的方法。从概念上讲,子进程指令段,数据段,堆栈段都是父进程的复制。从逻辑上看,可以理解子进程和父进程有各自独立的私有存储空间,并且子进程的初始状态是父进程的复制。了解了内核处理的这些技术,就不用担心这样做会占用很多内存。 必须有一种方法在程序中区分开父进程和子进程,让父子进程执行不同的程序代码,这样才有意义。 区分父子进程的方法就是依靠fork()系统调用的返回值。fork执行返回后,有两个执行流,两个进程(父和子)都收到返回值,是一个整数,但这两个值不相同。返回值很关键,它用于区分父进程和子进程。例如:
int p; p = fork(); 那么,这条语句可以分解为两个动作: ① 执行fork()系统调用; ② 给变量p赋值。 按照fork()系统调用的功能,执行完①后,新产生一个子进程,子进程是父进程的复制。接下来的操作②就会有两个进程分别在自己独立的地址空间内执行,各有自己独立的p变量。但是,操作系统对系统调用的处理导致函数调用fork()在父子进程中有不同的返回值。在对p赋值时,父进程的p得到一个正整数,而子进程的p得到0。程序据此区分父子进程,父进程得到的正整数是子进程的PID号。
【例7-7】 fork创建子进程,父子进程执行不同的程序段。 $ cat fork2.c int main(int argc, char *argv[]) { int p; printf("[1] %s: BEGIN\n", argv[0]); p = fork(); if (p > 0) { printf("[2] Parent, p=%d\n", p); } else if (p == 0) { printf("[3] Child, p=%d\n", p); } else { perror("Create new process"); } printf("[4] My PID %d, Parent's PID %d\n", getpid(), getppid()); printf("[5] %s: END\n", argv[0]); }
系统调用fork()返回值-1,那么,创建新进程失败。程序中的getpid()和getppid()是两个系统调用,分别用于获取进程在内核中PCB结构里记录的当前进程的PID号和当前进程的父进程的PID号。用户程序无法直接访问内核数据,必须使用系统调用。类似的系统调用还有一些,例如:getuid()获得当前进程的用户标识号等。系统调用fork()返回值-1,那么,创建新进程失败。程序中的getpid()和getppid()是两个系统调用,分别用于获取进程在内核中PCB结构里记录的当前进程的PID号和当前进程的父进程的PID号。用户程序无法直接访问内核数据,必须使用系统调用。类似的系统调用还有一些,例如:getuid()获得当前进程的用户标识号等。
上述程序的执行输出如下。这个例子中子进程先于父进程运行,也有可能父进程先于子进程运行。 上述程序的执行输出如下。这个例子中子进程先于父进程运行,也有可能父进程先于子进程运行。 $ ./fork2 [1] ./fork2: BEGIN [3] Child, p=0 [4] My PID 20796, Parent's PID 23950 [5] ./fork2: END [2] Parent, p=20796 [4] My PID 23950, Parent's PID 30320 [5] ./fork2: END fork返回值大于0,是子进程的PID;子进程的fork返回值为0。内核在实现fork调用时,首先初始化创建一个新的proc结构,然后复制父进程环境(包括user结构和内存资源)给子进程。
exec:重新初始化进程 exec系统调用,从一个指定的程序文件重新初始化一个进程。exec系统调用并没有创建新进程,进程还是旧的进程,只是exec调用将它重新初始化了指令段,用户数据段和堆栈段,系统数据段也几乎没有变化。应当说,exec系统调用是在旧进程中运行新程序。在组成进程的四个要素中,系统数据段PCB基本不变。调用exec时需要提供一个程序文件的名字,从磁盘上读入这一程序文件的内容,用这个程序文件中的CPU指令,重新初始化当前进程的指令段,当前进程的原指令段被丢弃;使用程序文件中的数据段说明,重新初始化当前进程的数据段,并从程序文件中获取数据段初值,
管道操作(1) • 创建管道 int pipe(int pfd[2]) • 创建一个管道,pfd[0]和pfd[1]分别为管道两端的文件描述字,pfd[0]用于读,pfd[1]用于写 • 管道写 ret = write(pfd[1], buf, n) • 若管道已满,则被阻塞,直到管道另一端read将已进入管道的数据取走为止 • 管道容量:某一有限值,如8192字节
管道操作(2) • 管道读 ret = read(pfd[0], buf, n) • 若管道写端已关闭,则返回0 • 若管道为空,且写端文件描述字未关闭,则被阻塞 • 若管道不为空(设管道中实际有m个字节) • n≥m,则读m个; • 如果n<m则读取n个 • 实际读取的数目作为read的返回值。 • 注意:管道是无记录边界的字节流通信 • 关闭管道close • 关闭写端则读端read调用返回0。 • 关闭读端则写端write导致进程收到SIGPIPE信号(默认处理是终止进程,该信号可以被捕捉) • 写端write调用返回-1,errno被设为EPIPE
父子进程管道通信 • 父进程关闭读端,子进程关闭写端 • 父进程 • fork后
管道通信:写端 /* File name : pwrite.c */ #define syserr(str) { perror(str); exit(1); } main() { int pfd[2]; char fdstr[10], *message = "Send/Receive data using pipe"; if (pipe(pfd) == -1) syserr("Create pipe"); switch(fork()) { case -1: syserr("fork"); case 0: /* child */ close(pfd[1]); sprintf(fdstr, "%d", pfd[0]); execlp("./pread", "pread", fdstr, 0); syserr("Execute pread file"); break; default: /* parent */ close(pfd[0]); if(write(pfd[1], message, strlen(message) + 1) == -1) perror("Write pipe"); break; } }
管道通信:读端 /* File name : pread.c */ #define syserr(str) { perror(str); exit(1); } main(int argc, char **argv) { int fd, nbyte, i; char buf[1024]; fd = strtol(argv[1], 0, 0); nbyte = read(fd, buf, sizeof(buf)); switch (nbyte) { case -1: syserr("Read pipe"); break; case 0: printf("Pipe closed\n"); break; default: printf("%d bytes [%s]\n", nbyte, buf); break; } exit(0); }