(P11)进程:复制进程映像、fork系统调用、孤儿进程、僵尸进程、写时复制

1.复制进程映像

  • 使用fork函数得到的子进程从父进程那里继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
  • 子进程与父进程的区别在于:
    (1)父进程设置的锁,子进程不继承
    (2)各自的进程id和父进程id不同
    (3)子进程的未决告警被消除
    (4)子进程的未决信号集设置为空集
  • 总结:
    (1)进程包括:代码段+数据段+堆栈段+PCB
    (2)fork之后,子进程复制父进程的代码段+数据段+堆栈段+PCB,只有PCB中的PID和PPID是不一样的
    (3)父子进程具有独立的4GB的地址空间(32bit的系统)

2.fork系统调用:调用一次,返回2次

  • 包含头文件<sys/types.h>和<unistd.h>
  • 函数功能:创建一个子进程
  • 函数原型
    pid_t在不同的系统,类型是不同的int short或者unsinged short,所以只能用%d的方式输出pid_t
pid_t fork(void);

参数:无参数
返回值:
如果成功创建一个子进程,对于父进程来说返回子进程id
如果成功创建一个子进程,对于子进程来说返回值为0
如果为-1,表示创建失败
  • eg:fork系统调用
    为啥父进程fork之后返回>0的值?,不返回=0?
    返回的就是子进程的id号码
    因为PCB中不可能保存子进程的id列表,这样会使得PCB膨胀,信息太大
    在这里插入图片描述
  • eg:代码:P11fork.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m);
        exit(EXIT_FAILURE);
    } while(0)

int main(int argc, char *argv[])
{
    // signal(SIGCHLD, SIG_IGN);
    printf("before fork pid = %d\n", getpid());
    
    pid_t pid;
    pid = fork();
    if (pid == -1)
        ERR_EXIT("fork error");
    if (pid > 0)
    {
        printf("this is parent pid=%d child pid =%d gval=%d\n", getpid(), pid);
        sleep(3);//防止父进程先结束
    }
    else if (pid = 0)
    {
        printf("this is child pid =%d parent pid =%d gval=%d\n", getpid(), getppid());
    }

    return 0;
}
  • 测试:
    父进程加不加sleep,可能出现的情况如下:
    父进程结束了,将控制权交给了shell提示符,子进程还没结束,子进程的输出信息就接在了shell提示符之后
    在这里插入图片描述
    父进程加上sleep,一定是子进程先运行结束,父进程才结束
    在这里插入图片描述
  • fork系统调用注意点
    (1)fork系统调用之后,父子进程将交替执行
    (2)如果父进程先退出,子进程还没退出,那么子进程的父进程将变为为init进程(注意:任何一个进程都必须有父进程
    (3)如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到子进程的退出状态才真正结束,否则这个时候子进程就称为僵进程。
    具体解释如下:
    fork一次调用两次返回的含义:fork成功意味着创建了一个进程副本,意味着有2个进程;
    2个进程在各自的进程地址空间中返回的
    父进程先结束,子进程还没有结束,那么子进程将称为孤儿进程,孤儿进程的含义是,其父进程是1号进程,托孤给1号进程;

3.孤儿进程、僵尸进程

  • eg:代码:P11defunc.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>


#define ERR_EXIT(m) \
    do \
    { \
        perror(m);
        exit(EXIT_FAILURE);
    } while(0)

int main(int argc, char *argv[])
{
    // signal(SIGCHLD, SIG_IGN);//可以避免僵尸进程,当子进程退出时,会向父进程发送SIGCHLD信号
    printf("before fork pid = %d\n", getpid());
    
    pid_t pid;
    pid = fork();
    if (pid == -1)
        ERR_EXIT("fork error");
    if (pid > 0)
    {
        printf("this is parent pid=%d child pid =%d gval=%d\n", getpid(), pid);
        sleep(100);
    }
    else if (pid = 0)
    {
        printf("this is child pid =%d parent pid =%d gval=%d\n", getpid(), getppid());
    }

    return 0;
}
  • 测试:
    子进程运行完毕了,父进程还没有查询到子进程的退出状态,此时子进程就处于僵尸状态
    defunct就表示僵尸状态,该子进程称之为僵尸进程
    而孤儿进程的含义是:
    父进程先退出,子进程就称为孤儿进程(1号进程)
    在这里插入图片描述

4.写时复制:解决系统如何实现fork?

  • 如果多个进程要读取他们自己的那部分资源的副本,那么复制是不必要的
    代码段是只读的,只需要共享即可。不会被修改的数据是共享的。

  • 每个进程只要保存一个指向这个资源的指针就可以了。

  • 如果一个进程要修改自己的那份资源的“副本”,那么就会复制那份资源。这就是写时复制的含义。
    LInux并不复制所有的资源,而只复制对应的页,其它的页并没有复制,仍然是共享的。

  • Makefile

.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=01fork 02defunc
all:$(BIN)
%.o:%.c
	$(CC) $(CFLAGS) -c $< -o $@
clean:
	rm -f *.o $(BIN)

©️2020 CSDN 皮肤主题: 点我我会动 设计师:上身试试 返回首页