问: 什么是进程
问: 进程是怎么产生的
问:操作系统是怎么识别各个进程的
pid
)来识别,pid
是进程在操作系统中的唯一标志操作系统中有一些进程ID是专用的:
#include
/*
* 功能:调用进程的进程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
#include
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
#include
#include
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
#include
#include
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
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
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);
}
#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);
}