3 POSIX 多任务及同步机制-实验1: fork创建进程

3 POSIX 多任务及同步机制-实验1: fork创建进程

一.实验目的

·掌握fork函数及其特点。
·通过实验深入理解操作系统的进程概念,Linux的进程概念。

二.实验背景

·Fork创建子进程中的问题
·进程的地址空间:教材P.187-188 newproc_posix.c代码
·父子进程的并发执行:教材P.188 newproc_posix.c代码执行结果以及两个问题
·Fork/exec流程:新进程覆盖子进程 (如果exec 函数调用成功,进程自己的执行代码就会变成加载程序的代码,execlp() 后面的代码也就不会执行了)
·孤儿进程和僵尸进程的形成原因

三.关键代码及分析

#include <stdio.h>
#include <stdlib.h> //for malloc
#include <string.h> //for mesmset,strcpy
#include <unistd.h>
#include <sys/types.h>

int main()
{
        pid_t pid;
        pid_t pid2;
        int var=88;
        char  *str = (char*)malloc(sizeof(char)*10);
        memset(str, 0x00, 10);

        /* fork a child process */
        pid = fork();

        if (pid < 0) { /* error occurred */
                fprintf(stderr, "Fork Failed\n");
                return 1;
        }
        else if (pid == 0) { /* child process */
                printf("I am the child %d\n",pid); //行A
                pid2 = getpid();
                printf("I am the child %d\n",pid2);
                strcpy(str, "child");
                var++;
                //sleep(50);
                }

        else { /* parent process */
                pid2 = getpid();
                printf("I am the parent %d and creae the child %d\n",pid2,pid); //行B
                strcpy(str, "parent");
                //sleep(50); //为了cat /proc/{pid}/maps
                wait(NULL);/* parent will wait for the child to complete */
                printf("Child Complete\n");
        }
    printf("str=%s, strAdd=%p, var=%d, varAdd=%p\n", str, str, var, &var);//行C
    return 0;
}

fork函数的返回值是一个pid_t 类型的数值。pid_t 是一个<sys/types.h>定义的宏,其实就是int。
在这里插入图片描述
当前进程调用fork函数会使操作系统创建一个新的进程(子进程),并且在任务表中为其建立一个新的task_struct 表项。新进程和原有进程的可执行程序是同一个程序。新进程的上下文和数据绝大部分是原进程(父进程)的副本。但是二者的进程标识符PID不同,它们是两个相互独立的进程!此时程序计数器PC在父、子进程的上下文中是相同的,都指向下一条要执行的语句。
fork函数的返回值在父子进程中是不同的。如果fork创建子进程成功,fork函数返回的值在父进程中是子进程的PID,而在子进程中返回的则是0:如果fok创建子进程失败,则返回负值。fork 失败的原因可能是系统中的进程数达到上限,或若系统中内存空间不够。所以fork函数具有一个特点:次调用 两次返回。一次调用是在父进程中fork函数被调用,返回则是分别在父、子进程中各自返回一次。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
pid_t pid;
pid_t pid2;
char buf[128];

        /* fork a child process */
        pid = fork();

        if (pid < 0) { /* error occurred */
                fprintf(stderr, "Fork Failed\n");
                return 1;
        }
        else if (pid == 0) { /* child process */
                printf("I am the child %d\n",pid);
                pid2 = getpid();
                printf("I am the child %d\n",pid2);
                sprintf(buf, "/proc/%d/maps",pid2);
                //execlp("/bin/cat","cat", buf,NULL); //行A
                printf("I am Child %d\n",getpid());     //行B
        }
        else { /* parent process */
                /* parent will wait for the child to complete */
                printf("I am the parent %d and creae the child %d\n",getpid(),pid);
                sleep(40);  // 为了运行cat /proc/父进程pid/maps
                wait(NULL);
                printf("Child Complete\n");
        }

    return 0;
}
int execlp(const char * file, const char * arg, ..., (char *)0);

该函数会从系统环境变量PATH所指的目录中查找符合参数file的文件名,找到后就行执行该程序,然后将第二个以后的参数作为该程序的argv[0]、argv[1]、…参数,最后一个参数必须用空指针(NULL)表示结束。如果用常数0来表示一个空指针,则必须将它强制转能为一个字符指针。如果exec 函数调用成功,进程自己的执行代码就会变成加载程序的代码,execlp() 后面的代码也就不会执行了。

· pid_t wait(int * status);
调用wait函数会暂时停止当前进程的执行,直到有信号到来或子进程结束。如果在调用wait()时子进程已经结束,则wait函数会立即返回子进程结束状态值。子进程的结束状态值会由参数status返回,面子进程的进程识别码也会一起返回。 如果不在意结束状态值,则参数status可以设成NULL.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{
        int a;
        int pid;
        printf("AAAAAAAA");//print 1;
        pid=fork();
        if(pid==0){
                printf("\n child\n");
        }
        else if(pid>0){
                printf("\n parent\n");
        }
        printf("BBBBBBB\n");//print 2; 父子进程都会打印;
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
        pid_t pid;
        if((pid=fork())==-1)
                perror("fork");
        else if(pid==0)
        {
                printf("child : pid=%d,ppid=%d\n",getpid(),getppid());//输出子进程pid以及其父进程pid,此时父进程未退出
                sleep(2);//休眠两秒等待父进程先退出
                printf("child : pid=%d,ppid=%d\n",getpid(),getppid());//请注意此次输出子进程的ppid,与上一次的差异,此时父进程已退出
        }
        else
        {
                printf("father : pid=%d,ppid=%d\n",getpid(),getppid());
                sleep(1);
                exit(0);//休眠一秒后直接退出,未使用wait调用。此时子进程相当于被“无情的抛弃了”
        }
}

当一个子进程结束运行时,子进程的退出状态(返回值)会报告给操作系统,系统则以特中的信号将子进程结束的事件通知父进程,此时子进程的进程控制块task_struct 仍驻留在内存中。一般来说,收到子进程结束的通知后,父进程会使用wait系统调用获取子进程的提出状态,然后内核就可以从任务表中删除已结束的子进程的task_struct结构体;如果父并程没有这么做的话子进程的task_struct就会一 直庄留在系统中,成为僵尸进程。 孤儿进程则是指父进程结束后仍在运行的进程,在Linux系统中,孤儿进程一般会被init进程“收养”,成为init的子进程。

四.实验结果与分析

gcc -o newproc-posix newproc-posix.c //编译源代码生成可执行文件

在这里插入图片描述

./newproc-posix //运行可执行程序

在这里插入图片描述

fork函数创建子进程成功后,父进程与子进程的执行顺序是无法确定的。假设父进程先运行,根据程序计数器父进程将运行到if语句。在父进程中fork 返回的是子进程的PID,也就是说,此时程序中的pid变量大于0,因此pid<0和pid == 0的两个if分支都不会运行,父进程只运行pid>0对应的f分支语句。子进程在之后的某个时候得到调度,它的task_ struct 信息被读出,进程执行上下文被换人,根据程序计数器子进程同样运行到if语句。在子进程中fork调用返回值为0,也就是说,此时程序中的pid变量等于0,所以子进程只运行pid==0的话分支语句。此时运行的不是父进程了,虽然是同一个程序,但是这是同一个程序的另外次执行。 在操作系统中这次执行由另外一个进程使用另一个task strucet 表示的,从执行的角度说子进程和父进程是相互独立的。
·去掉sleep(50)的注释
在这里插入图片描述
gcc -o newproc-posix2 newproc-posix2.c // 编译源代码生成可执行文件
在这里插入图片描述

./newproc-posix2 //执行可执行文件

在这里插入图片描述

·去掉execlp()前面的注释

在这里插入图片描述

当执行到到execlp("/bin/cat",“cat”, buf,NULL)时,该命令指定运行的程序是cat,参数为/proc/{pid}/maps。结果显示当调用了execlp()函数后子进程的执行程序不再是newproc-posix2,而是/bin/cat。说明如果execlp() 函数调用成功,进程自己的执行代码就会变成加载程序的代码,execlp() 后面的代码也就不会执行了。

gcc -o fork_io fork_io.c //编译源代码生成可执行文件

在这里插入图片描述

./fork_io // 执行可执行文件

在这里插入图片描述

理论由于A 在fork函数之前,B 在fork函数之后。在执行fork函数时,行A已经被执行了,父、子进程的程序计数器都应该指向了fork 语句后的下一句。故此A输出一次,B在父、子进程各输出一次。但实际都输出了两次,原因在于用户空间和内核空间的数据传输机制上。当用户程序执行到行A时,一方面printf语句并不是自己完成打印的,而需要调用Linux提供的系统调用完成输出,在这个过程中数据会写人到进程在内核的缓冲区中,另一方面 printf没有要求立即完成输出.所以系统等待合适的时机再把内核缓冲区中的数据输出。因此当fork函数被调用时,系统会复制父进程的代码段、数据段等给子进程,所以相关数据也被一起复制,这样就导致字符串 AAAAAAA被输出了两次,如果让行A只执行一次,可以改写该句为:
printf(“AAAAAAAA\n”);

系统看到 \n则会立即刷新缓冲区,完成输出工作。在这种情况下,执行fork 函数时,父进程内核缓冲区中就不存在该数据了,子进程也就不会再打印一次了。
在这里插入图片描述
在这里插入图片描述

#gcc -o orphan_process orphan_process.c //编译源代码生成可执行文件
在这里插入图片描述

./orphan_process //运行可执行文件

在这里插入图片描述

fork函数调用成功后,父进程先执行,pid大于0,执行printf(“father : pid=%d,ppid=%d\n”,getpid(),getppid()); , 由于后面父进程进入休眠,那么父进程进入等待状态,一秒后进入就绪状态,在这个过程中子进程执行,子进程中pid=0,执行 printf(“child : pid=%d,ppid=%d\n”,getpid(),getppid()),可以看出此时的父进程还没退出,子进程的ppid=11295为父进程的pid号;子进程执行sleep(2)休眠2s进入等待状态,2s后进入就绪状态,在等待的时候父进程执行exit(0),父进程退出,由于子进程还没退出,父进程退出了,那么子进程被init进程“收养”,成为init的子进程,当子进程再次执行是,答打印的ppid=1,为init进程的pid号,此时的子进程为孤儿进程。

发布了170 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/wct3344142/article/details/104133077
今日推荐