赞
踩
问: 什么是进程
问: 进程是怎么产生的
问:操作系统是怎么识别各个进程的
pid
)来识别,pid
是进程在操作系统中的唯一标志操作系统中有一些进程ID是专用的:
#include <unistd.h> /* * 功能:调用进程的进程ID */ __pid_t getpid (void) /* * 功能:调用进程的父进程ID */ __pid_t getppid (void) /* * 功能:调用进程的实际用户ID */ __uid_t getuid (void) /* * 功能:调用进程的有效用户ID */ __uid_t geteuid (void) /* * 功能:调用进程的有效组ID */ __gid_t getegid (void) //上面这些函数都没有出错返回
库函数 exit()位于系统调用_exit()之上。这里只是强调,在调用fork()之后,父、子进程中一般只有一个会通过调用 exit()退出,而另一进程则应使用_exit()终止。
系统调用wait(&status)的目的有二:
系统调用 execve(pathname,argv,envp)加载一个新程序(路径名为pathname,参数列表为argv,环境变量列表为envp)到当前进程的内存。这将丢弃现存的程序文本段,并为新程序重新创建栈、数据段以及堆。通常将这一动作称为执行(execing)一个新程序。
下图对 fork()、exit()、wait()以及 exece()之间的相互协同作了总结。(此图勾勒了 shell 执行一条命令所历经的步骤:shell 读取命令,进行各种处理,随之创建子进程以执行该命令,如此循环不已。
SUSv3 将 vfork()标记为已过时,SUSv4 则进一步将其从规范中删除。所以应尽量避免使用vfork()。
#include<unistd.h>
#include<sys/types.h>
pid_t fork( void);
返回值:
注意:
问: 父子进程的关系
问: 父子进程的运行时机
这种不确定性可能会导致所谓“竞争条件(race condition)”的错误
问:fork函数返回的值为什么在父子进程中不同?
程序代码可以通过fork()的返回值来区分父、子进程。
其实就相当于链表,进程形成了链表,父进程的fork函数返回的值指向子进程的进程id, 因为子进程没有子进程,所以其fork函数返回的值为0.
问:父进程和子进程之间的区别
fork
的返回值不同tms_utime
、tms_stime
、tms_cutime
和tms_ustime
的值设置为0问: fork失败的主要原因
当无法创建子进程时,fork()将返回-1。
问: 父进程正常运行,子进程终止时会发生什么?
SIGCHLD
信号。总结
要理解fork()的诀窍的关键是,要意识到,完成对其调用后将存在两个进程,而且而且进程都会从fork()的返回值继续执行
这两个进程将执行相同的程序文本段,却各自拥有不同的栈段、数据段以及堆段拷贝。刚开始时,子进程的栈段、堆段、数据段时对父进程内存相应各部分的完全复制。执行fork()之后,每个进程均可以修改各自的栈段、堆段和数据段,而不影响另一进程。
fork函数被调用一次但返回两次
#include<unistd.h> #include<stdio.h> #include<stdlib.h> int main(int argc,char *argv[]){ pid_t pid=fork(); if ( pid < 0 ) { fprintf(stderr,"错误!"); } else if( pid == 0 ) { printf("子进程空间"); exit(0); } else { printf("父进程空间,子进程pid为%d",pid); } // 可以使用wait或waitpid函数等待子进程的结束并获取结束状态 exit(0); }
子进程的栈段、数据段、堆段是父进程的拷贝
#include <fcntl.h> #include <zconf.h> #include <stdio.h> int globvar = 6; /* 全局变量在数据段 */ char buf[] = "a write to stdout\n"; int main(void) { int var; /*自动变量在栈段 */ pid_t pid; var = 88; if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1){ printf("write error"); _exit(0); } printf("before fork\n"); if ((pid = fork()) < 0) { printf("fork error"); _exit(0); } else if (pid == 0) { /* 子进程(子进程运行的代码段从这里开始,这行之前的不执行)*/ globvar++; /* 修改全局变量和局部变量 */ var++; } else { sleep(2); /* 父进程:睡觉2s以便子进程先运行 */ } printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var); _exit(0); }
从上面可以看出:
问: 如果将write改成待缓冲的写入,会输出什么?
问:strlen与sizeof的区别
sizeof
在编译时计算缓冲区长度执行fork()时,子进程会获得父进程所有文件描述符的副本。这些副本的创建方式类似于dup(),这也意味着父、子进程中对应的描述符均指向相同的打开文件句柄。打开文件句柄包含有当前文件偏移量(由 read()、write()和 lseek()修改)以及文件状态标志(由 open()设置,通过 fcntl()的 F_SETFL 操作改变)。一个打开文件的这些属性因之而在父子进程间实现了共享。举例来说,如果子进程更新了文件偏移量,那么这种改变也会影响到父进程中相应的描述符
#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h> #include <zconf.h> int main(int argc, char *argv[]) { int fd, flags; char tmplate[] = "/tmp/testXXXXXX"; setbuf(stdout, NULL); /* Disable buffering of stdout */ /* Open a temporary file, set its file offset to some arbitrary value, and change the setting of one of the open file status flags. */ fd = mkstemp(tmplate); if (fd == -1){ perror("mkstemp"); exit(EXIT_FAILURE); } printf("File offset before fork(): %lld\n", (long long) lseek(fd, 0, SEEK_CUR)); flags = fcntl(fd, F_GETFL); if (flags == -1){ perror("fcntl F_GETFL"); exit(EXIT_FAILURE); } printf("O_APPEND flag before fork() is: %s\n", (flags & O_APPEND) ? "on" : "off"); switch (fork()) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: /* Child: change file offset and status flags */ if (lseek(fd, 1000, SEEK_SET) == -1){ perror("lseek SEEK_SET"); exit(EXIT_FAILURE); } flags = fcntl(fd, F_GETFL); /* Fetch current flags */ if (flags == -1){ perror("fcntl --- F_GETFL"); exit(EXIT_FAILURE); } flags |= O_APPEND; /* Turn O_APPEND on */ if (fcntl(fd, F_SETFL, flags) == -1){ perror("fcntl --- F_SETFL"); exit(EXIT_FAILURE); } _exit(EXIT_SUCCESS); default: /* Parent: can see file changes made by child */ if (wait(NULL) == -1){ perror("wait"); exit(EXIT_FAILURE); } /* Wait for child exit */ printf("Child has exited\n"); printf("File offset in parent: %lld\n", (long long) lseek(fd, 0, SEEK_CUR)); flags = fcntl(fd, F_GETFL); if (flags == -1){ perror("fcntl F_GETFL "); exit(EXIT_FAILURE); } printf("O_APPEND flag in parent is: %s\n", (flags & O_APPEND) ? "on" : "off"); exit(EXIT_SUCCESS); } }
如果不需要这种对文件描述符的共享方式,那么在设计应用程序时,应于 fork()调用后注意两点:其一,令父、子进程使用不同的文件描述符;其二,各自立即关闭不再使用的描述符(亦即那些经由其他进程使用的描述符)。
从概念上来说,可以将fork()认做是对父进程程序段、数据段、堆段以及栈段创建拷贝。早期的Unix实现中,此类复制确实是如此:将父进程内存拷贝至交换空间,以此创建新进程映像,而在父进程保持自身内存的同时,将换出映像置为子进程。不过,真要是简单地将父进程虚拟内存页拷贝到新的子进程,那就太浪费了。原因有很多,其中之一是:fork()之后尝尝伴随着exec(),这会用新程序替换进程的代码段,并重新初始化其数据段、堆段和栈段。大部分现代Unix实现中采用两种技术来避免这种浪费:
调用 fork()后,无法确定父、子进程间谁将率先访问 CPU。不应对 fork()之后执行父、子进程的特定顺序做任何假设。若确需保证某一特定执行顺序,则必须采用某种同步技术,比如信号量(semaphore)、文件锁(file lock)以及进程间经由管道(pipe)的消息发送等。接下来我们使用同步信号以规避 fork()之后的竞争条件
#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h> #include <zconf.h> #include <time.h> #include <errno.h> char *currTime(const char *format) { #define BUF_SIZE 1000 static char buf[BUF_SIZE]; /* Nonreentrant */ time_t t; size_t s; struct tm *tm; t = time(NULL); tm = localtime(&t); if (tm == NULL) return NULL; s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm); return (s == 0) ? NULL : buf; } #define SYNC_SIG SIGUSR1 /* Synchronization signal */ static void /* Signal handler - does nothing but return */ handler(int sig) { } int main(int argc, char *argv[]) { pid_t childPid; sigset_t blockMask, origMask, emptyMask; struct sigaction sa; setbuf(stdout, NULL); /* Disable buffering of stdout */ sigemptyset(&blockMask); sigaddset(&blockMask, SYNC_SIG); /* Block signal */ if (sigprocmask(SIG_BLOCK, &blockMask, &origMask) == -1){ perror("sigprocmask"); exit(EXIT_FAILURE); } sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = handler; if (sigaction(SYNC_SIG, &sa, NULL) == -1){ perror("sigaction"); exit(EXIT_FAILURE); } switch (childPid = fork()) { case -1: perror("fork"); exit(EXIT_FAILURE); case 0: /* Child */ /* Child does some required action here... */ printf("[%s %ld] Child started - doing some work\n", currTime("%T"), (long) getpid()); sleep(2); /* Simulate time spent doing some work */ /* And then signals parent that it's done */ printf("[%s %ld] Child about to signal parent\n", currTime("%T"), (long) getpid()); if (kill(getppid(), SYNC_SIG) == -1){ perror("kill"); exit(EXIT_FAILURE); } /* Now child can do other things... */ _exit(EXIT_SUCCESS); default: /* Parent */ /* Parent may do some work here, and then waits for child to complete the required action */ printf("[%s %ld] Parent about to wait for signal\n", currTime("%T"), (long) getpid()); sigemptyset(&emptyMask); if (sigsuspend(&emptyMask) == -1 && errno != EINTR){ perror("sigsuspend"); exit(EXIT_FAILURE); } printf("[%s %ld] Parent got signal\n", currTime("%T"), (long) getpid()); /* If required, return signal mask to its original state */ if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1){ perror("sigprocmask"); exit(EXIT_FAILURE); } /* Parent carries on to do other things... */ exit(EXIT_SUCCESS); } }
#include <fcntl.h> #include <zconf.h> #include <stdio.h> #include <sys/wait.h> int main(void) { pid_t pid; if ((pid = fork()) < 0) { printf("fork error"); _exit(0); } else if (pid == 0) { //first child printf(" first child from main fork : curr pid = %ld, parent pid = %ld\n", (long)getpid(), (long)getppid()); if ((pid = fork()) < 0){ printf("fork error"); _exit(0); }else if (pid > 0){ printf(" first child from main fork : curr pid = %ld, parent pid = %ld\n", (long)getpid(), (long)getppid()); // sleep(10); _exit(0); } sleep(2); printf("second child from first child: pid = %d, getpid = %ld, parent pid(because first child had exit, parent changed init proceess, so parent child = 1) = %ld\n",pid, (long)getpid(), (long)getppid()); _exit(0); } printf(" main fork : curr pid = %ld, pid = %ld\n", (long)getpid(), (long)pid); if (waitpid(pid, NULL, 0) != pid){ /* wait for first child : waitpid会暂时停止目前进程的执行,直到有信号来到或子进程结束。 */ printf("waitpid error"); _exit(0); } printf("main exit\n"); _exit(0); }
init
进程
#include "apue.h" static void charatatime(char *); int main(void) { pid_t pid; if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { charatatime("output from child child child child child child child child child\n"); } else { charatatime("output from parent parent parent parent parent parent parent parent parent\n"); } exit(0); } static void charatatime(char *str) { char *ptr; int c; setbuf(stdout, NULL); /* set unbuffered: 设置为无缓冲之后,每次字符输出都会调用一次write */ for (ptr = str; (c = *ptr++) != 0; ) putc(c, stdout); }
#include "apue.h" static void charatatime(char *); int main(void) { pid_t pid; TELL_WAIT(); // 告知需要等待 if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { WAIT_PARENT(); /* 等待父进程结束之后再运行 */ charatatime("output from child child child child child child child child child\n"); } else { charatatime("output from parent parent parent parent parent parent parent parent parent\n"); TELL_CHILD(pid); // 告知子进程已经退出了 } exit(0); } static void charatatime(char *str) { char *ptr; int c; setbuf(stdout, NULL); /* set unbuffered */ for (ptr = str; (c = *ptr++) != 0; ) putc(c, stdout); }
#include "apue.h" static void charatatime(char *); int main(void) { pid_t pid; TELL_WAIT(); if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { charatatime("output from child child child child child child child child child\n"); TELL_PARENT(getppid()); } else { WAIT_CHILD(); charatatime("output from parent parent parent parent parent parent parent parent parent\n"); TELL_CHILD(pid); } exit(0); } static void charatatime(char *str) { char *ptr; int c; setbuf(stdout, NULL); /* set unbuffered */ for (ptr = str; (c = *ptr++) != 0; ) putc(c, stdout); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。