t# fork与逻辑运算
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
fork();
fork()&&fork()||fork();
fork();
printf("main\n");
}
1.A&&B,若A = 0,则不会执行B。
A||B,若A != 0,则不会执行B。
2.特别提示:A&&B||C,当A=0时仅仅不执行B,但还会执行C。
3.如图所示,为fork()&&fork()||fork()的流程图。
由于fork()一次调用,两次返回的特性,无条件限制时fork()乘2,在此代码中表达式之前,之后各有一个fork(),故一共有5*4=20个程 序,除去原始的一个,则还余下新建的19个。
缓存的问题
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
int i;
for(i=0; i<2; i++){
fork();
printf("-");
}
wait(NULL);
wait(NULL);
return 0;
}
1.会打印8个“-”,因为没有\n,所以不会直接刷新缓存输出到设备上,在fork时,子进程会复制到父进程的缓存内容,导致多输出两个“-”。
main函数:
/*
* forks.c - Examples of Unix process control
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
int main(int argc, char *argv[])
{
int option = 0;
if (argc > 1)
option = atoi(argv[1]);
switch(option) {
case 0: fork0();
break;
case 1: fork1();
break;
case 2: fork2();
break;
case 3: fork3();
break;
case 4: fork4();
break;
case 5: fork5();
break;
case 6: fork6();
break;
case 7: fork7();
break;
case 8: fork8();
break;
case 9: fork9();
break;
case 10: fork10();
break;
case 11: fork11();
break;
case 12: fork12();
break;
case 13: fork13();
break;
case 14: fork14();
break;
case 15: fork15();
break;
case 16: fork16();
break;
case 17: fork17();
break;
default:
printf("Unknown option %d\n", option);
break;
}
return 0;
}
fork0
/*
* fork0 - The simplest fork example
* Call once, return twice
* Creates child that is identical to parent
* Returns 0 to child process
* Returns child PID to parent process
*/
void fork0()
{
if (fork() == 0) {
printf("Hello from child\n");
}
else {
printf("Hello from parent\n");
}
}
1.调用一次,返回两次:fork函数被父进程调用一次,返回的是新创建的子进程和父进程。
2.并发:父进程和子进程独立运行,结果显示在我的系统上为父进程先完成printf,输出Hello from parent,子进程后完成printf,输出 Hello from child,但是在另外的系统上可能正好相反。
进程图如下所示:
fork1
/*
* fork1 - Simple fork example
* Parent and child both run same code
* Child starts with identical private state
*/
void fork1()
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
}
else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
}
1.fork()创建子进程时会获得父进程的代码和数据,故子进程中的x初始值也=1。
2.\n 换行并在输出时刷新缓存。
3.getpid()函数:获取当前进程号。
4.进程号:子进程的id号在父进程之后一个。
5.由于并发故可能出现类似顺序:
Parent has x = 0
Child has x=2
Bye from process 23080 with x = 0
Bye from process 23081 with x = 2
不回跳即可。
流程图如下:
fork2
/* fork2 - Two consecutive forks
* Both parent and child can continue forking
* Ordering undetermined
*/
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
}
流程图如下:
fork3
/*
* fork3 - Three consective forks
* Parent and child can continue forking
*/
void fork3()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("L2\n");
fork();
printf("Bye\n");
}
流程图如下:
fork4
/*
* fork4 - Nested forks in parents
*/
void fork4()
{
printf("L0\n");
if (fork() != 0) {
printf("L1\n");
if (fork() != 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
特别提示:编程规范——首行缩进;注意作用域,不要犯低级错误。
![fork4
fork5
/*
* fork5 - Nested forks in children
*/
void fork5()
{
printf("L0\n");
if (fork() == 0) {
printf("L1\n");
if (fork() == 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
类似fork4
流程图如下:
fork6
void cleanup(void) {
printf("Cleaning up\n");
}
/*
* fork6 - Exit system call terminates process
* call once, return never
*/
void fork6()
{
atexit(cleanup);
fork();
exit(0);
}
atexit() : 注册终止函数,即完成各种清除操作,函数原型为void atexit(void (*func)(void));
atexit()函数调用时机:(1-5:正常;6-8:异常)
1)从main函数返回;
2) 调⽤exit函数;
3) 调⽤_exit或_Exit;
4)最后⼀个线程从启动例程返回;
5) 最后⼀个线程调⽤pthread_exit;
6) 调⽤abort函数;
7) 接到⼀个信号并终⽌;
8) 最后⼀个线程对取消请求做出响应调⽤exit函数;
我们这里用了exit,故出现了两次Cleaning up!
(atexit函数的调用顺序是和登记顺序相反的)
fork7
/*
* fork7 - Demonstration of zombies.
* Run in background and then perform ps
*/
void fork7()
{
if (fork() == 0) {
/* Child */
printf("Terminating Child, PID = %d\n", getpid());
exit(0);
} else {
printf("Running Parent, PID = %d\n", getpid());
while (1)
; /* Infinite loop */
}
}
1.一个进程终止时,内核并不是立即把它清除掉而是让进程保持在一种已终止的状态,直到它的父进程被回收,结合代码,可以看出,父进程中有一个无限循环,导致父进程迟迟不能被回收,故我们运行时程序会陷入一个死循环。
2.CTRL-Z和CTRL-C都是中断命令但是作用不同:
CTRL-C是中断。
CTRL-Z是挂起,还可以继续前台或后台的任务。
(fg命令重新启动前台被中断的任务,bg命令把被中断的任务放在后台执行)
fork8
/*
* fork8 - Demonstration of nonterminating child.
* Child still running even though parent terminated
* Must kill explicitly
*/
void fork8()
{
if (fork() == 0) {
/* Child */
printf("Running Child, PID = %d\n",
getpid());
while (1)
; /* Infinite loop */
} else {
printf("Terminating Parent, PID = %d\n",
getpid());
exit(0);
}
}
不会出现上述fork7的问题,关闭终端时也不会有提示kill。
印证了两个进程独立的特性。
fork9
/*
* fork9 - synchronizing with and reaping children (wait)
*/
void fork9()
{
int child_status;
if (fork() == 0) {
printf("HC: hello from child\n");
exit(0);
} else {
printf("HP: hello from parent\n");
wait(&child_status);
printf("CT: child has terminated\n");
}
printf("Bye\n");
}
1.wait()函数原型:int wait(int *status)
2.功能:父进程一旦调用了wait就立即阻塞自己,如果让它找到变成僵尸的子进程,wait就会收集这个子进程的信息,正常情况下wait()的返回值为子进程的PID(异常:wait()的返回值则为-1)并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞,直到出现为止。
故运行结果才出现HC在前,CT在后的情况。
fork10
#define N 5
/*
* fork10 - Synchronizing with multiple children (wait)
* Reaps children in arbitrary order
* WIFEXITED and WEXITSTATUS to get info about terminated children
*/
void fork10()
{
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
exit(100+i); /* Child */
}
for (i = 0; i < N; i++) { /* Parent */
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
1.WIFEXITED():这个宏用来指出子进程是否为正常退出的,如果正常退出则返回一个非零值。
2.WEXITSTATUS():当WIFEXITED返回子进程的返回值,看子进程调用的exit()的参数,在此代码中子进程调用exit(100), exit(101)exit(102)exit(103)exit(104),故WEXITSTATUS(status)返回100,101,102,103,104
3.由于exit倒序收回,故输出为104,103,102,101,100。
4.pid_t:自定义类型,进程id 的类型。
fork11
/*
* fork11 - Using waitpid to reap specific children
* Reaps children in reverse order
*/
void fork11()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
exit(100+i); /* Child */
for (i = N-1; i >= 0; i--) {
pid_t wpid = waitpid(pid[i], &child_status, 0);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
/*********
* Signals
*********/
waitpid():
函数原型:pid_t waitpid(pid_t pid,int *status,int options)
功能:让父进程知道某一个自己创建的子进程何时结束。
参数说明:
1)pid_t pid
父进程欲等待的子进程识别码,其具体含义如下:
pid<-1 等待进程组号为pid绝对值的任何子进程。
pid=-1 等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。
pid=0 等待进程组号与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。
pid>0 等待进程号为pid的子进程。
2)int *status
该参数将保存子进程的状态信息。如果status不是空指针,则状态信息将被写入,如果不关心子进程的状态的话,可以传入空指针。
3)int options
提供了一些另外控制waitpid()函数的行为的选项来。如果不想使用这些选项,则可以把这个参数设为0。
waitpid不按照特定的顺序等待他的所有子进程终止。
fork12
/*
* fork12 - Sending signals with the kill() function
*/
void fork12()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
/* Child: Infinite Loop */
while(1)
;
}
for (i = 0; i < N; i++) {
printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT);
}
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminated abnormally\n", wpid);
}
}
1.kill():
函数原型:int kill(pid_t pid, int sig);
功能:用于向任何进程组或进程发送信号。
2.此代码中的kill(pid[i], SIGINT);
pid大于零时,pid是信号欲送往的进程的标识。
SIGINT程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
以此种方式终止了无限循环的子进程。
3.最后一个循环,显示了子进程的终止情况,同样,是创建顺序的倒序终止。
fork13
/*
int_handler - SIGINT handler
*/
void int_handler(int sig)
{
printf("Process %d received signal %d\n", getpid(), sig); /* Unsafe */
exit(0);
}
/*
* fork13 - Simple signal handler example
*/
void fork13()
{
pid_t pid[N];
int i;
int child_status;
signal(SIGINT, int_handler);
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
/* Child: Infinite Loop */
while(1)
;
}
for (i = 0; i < N; i++) {
printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT);
}
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminated abnormally\n", wpid);
}
}
1.signal()
功能: 设置某一信号的对应动作。
原型: sighandler_t signal(int signum, sighandler_t handler)然后退出。
signum:指明了所要处理的信号类型。
handler:描述了与信号关联的动作。
2.此代码中 signal(SIGINT, int_handler);
SIGINT:由Interrupt Key产生,通常是CTRL+C或者DELETE。
int_handler:关联的动作为输出Process 子进程ID received signal 信号
3.整个流程就是:终止程序,输出对应动作,然后退出。
fork14
/*
* child_handler - SIGCHLD handler that reaps one terminated child
*/
int ccount = 0;
void child_handler(int sig)
{
int child_status;
pid_t pid = wait(&child_status);
ccount--;
printf("Received SIGCHLD signal %d for process %d\n", sig, pid); /* Unsafe */
fflush(stdout); /* Unsafe */
}
/*
* fork14 - Signal funkiness: Pending signals are not queued
*/
void fork14()
{
pid_t pid[N];
int i;
ccount = N;
signal(SIGCHLD, child_handler);
for (i = 0; i < N; i++) {
if ((pid[i] = fork()) == 0) {
sleep(1);
exit(0); /* Child: Exit */
}
}
while (ccount > 0)
;
}
1.fflush(stdout)
stdout是行缓冲的,输出先放在一个buffer里面,换行时输出到屏幕。
fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上。
2.signal(SIGCHLD, child_handler);
SIGCHLD 进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略。
child_handlel指定的动作,本代码中输出Received SIGCHLD signal 信号 for process 进程 ,并刷新到输出设备上。
3.sleep(1);
linux下的sleep函数原型为: unsigned int sleep(unsigned int seconds);
sleep(n)单位为秒。
功能:将目前动作暂停指定的时间。
4. child_handler只回收一个故可以看到只回收了一个子进程。
fork15
/*
* child_handler2 - SIGCHLD handler that reaps all terminated children
*/
void child_handler2(int sig)
{
int child_status;
pid_t pid;
while ((pid = wait(&child_status)) > 0) {
ccount--;
printf("Received signal %d from process %d\n", sig, pid); /* Unsafe */
fflush(stdout); /* Unsafe */
}
}
/*
* fork15 - Using a handler that reaps multiple children
*/
void fork15()
{
pid_t pid[N];
int i;
ccount = N;
signal(SIGCHLD, child_handler2);
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
sleep(1);
exit(0); /* Child: Exit */
}
while (ccount > 0) {
pause();
}
}
1.pause();
使调用进程暂停,直到接收到信号,要么终止,或导致它调用一个信号捕获函数。
2.child_handlel指定的动作,本代码中输出Received signal 信号 from process 进程 ,并刷新到输出设备上。
3.child_handler2中,阻塞返回子进程的状态,正常终止都有动作,故回收多个进程。
fork16
/*
* fork16 - Demonstration of using /bin/kill program
*/
void fork16()
{
if (fork() == 0) {
printf("Child1: pid=%d pgrp=%d\n",
getpid(), getpgrp());
if (fork() == 0)
printf("Child2: pid=%d pgrp=%d\n",
getpid(), getpgrp());
while(1);
}
}
1.getpid():返回当前进程标识。
2.getpgrp():函数返回进程ID的进程组,如果未指定,则返回当前进程组。
fork17
/*
* Demonstration of using ctrl-c and ctrl-z
*/
void fork17()
{
if (fork() == 0) {
printf("Child: pid=%d pgrp=%d\n",
getpid(), getpgrp());
}
else {
printf("Parent: pid=%d pgrp=%d\n",
getpid(), getpgrp());
}
while(1);
}
1.CTRL-Z和CTRL-C都是中断命令但是作用不同。
CTRL-C是中断。
CTRL-Z是挂起,还可以继续前台或后台的任务。
家庭作业
8.11
#include<stdio.h>
int main()
{
int i;
for(i=0;i<2;i++)
fork();
printf("hello\n");
exit(0);
}
8.12
#include<stdio.h>
void doit()
{
fork();
fork();
printf("hello\n");
return;
}
int main()
{
doit();
printf("hello\n");
exit(0);
}
8.13
#include<stdio.h>
int main()
{
int x=3;
if(fork()!=0)
printf("x=%d\n",++x);
printf("x=%d\n",--x);
exit(0);
}
8.14
#include<stdio.h>
void doit()
{
if(fork()==0){
fork();
printf("hello\n");
exit(0);
}
return;
}
int main()
{
doit();
printf("hello\n");
exit(0);
}
8.15
#include<stdio.h>
void doit()
{
if(fork()==0){
fork();
printf("hello\n");
return;
}
return;
}
int main()
{
doit();
printf("hello\n");
exit(0);
}
注意return是返回调用的主函数,而exit是退出程序。
8.16
#include<stdio.h>
#include<sys/wait.h>
int counter=1;
int main()
{
if(fork()==0){
counter--;
exit(0);
}
else{
wait(NULL);
printf("counter=%d\n",++counter);
}
exit(0);
}
8.18
#include<stdio.h>
void end(void)
{
printf("2");
fflush(stdout);
}
int main()
{
if(fork()==0)
atexit(end);
if(fork()==0){
printf("0");fflush(stdout);
}
else{
printf("1");fflush(stdout);
}
exit(0);
}
012021
8_21
#include<stdio.h>
#include<sys/wait.h>
int main()
{
if(fork()==0){
printf("a"); fflush(stdout);
exit(0);
}
else{
printf("b"); fflush(stdout);
waitpid(-1,NULL,0);
}
printf("c");fflush(stdout);
exit(0);
}