创建两个进程

文章目录

创建两个进程

2. 实现思路及源代码

2.1 实现思路

2.1.1 fork() 函数

fork() 是 Linux/Unix 系统中的一个系统调用,用于创建一个新的进程。这个新的进程(称为子进程)是父进程的复制品,并从 fork() 语句之后开始执行。
fork() 返回值如下:

  • 在父进程中:返回子进程的 PID。
  • 在子进程中:返回 0。
  • 失败时:返回 -1。
2.1.2 思路分析

​ 由2.1.1可知,在处于子进程时,fork返回的PID=0;在处于父进程时,fork返回的PID>0;而实验需要创建两个子进程,也就是在父进程完成第一个子进程创建后,通过if判断语句,判断当前是否处于父进程(PID>0);如果处于父进程,则再次创建子进程。这样就

举个例子:假设父进程 PID 是 1000,执行的顺序如下:

  1. 父进程 1000 调用 fork(),创建 子进程 1 (1001)。
  2. 子进程 1 (1001) 进入 if (pid1 == 0) 分支 ,打印信息后 return 退出。
  3. **父进程 1000 继续执行 pid2 = fork();,创建 子进程 2 (1002)
  4. 子进程 2 (1002) 进入 if (pid2 == 0) 分支 ,打印信息后 return 退出。
  5. 父进程 1000 继续执行,打印父进程对应的信息。

2.2 源代码

复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    pid_t pid1, pid2;

    pid1 = fork(); // 创建第一个子进程
    if (pid1 < 0) {
        perror("fork failed");
        return 1;
    }

    if (pid1 == 0) {
        // 子进程1
        printf("Child 1: PID = %d, Parent PID = %d\n", getpid(), getppid());
        sleep(15); // 保持进程存活
        return 0;
    } else {
        pid2 = fork(); // 创建第二个子进程
        if (pid2 < 0) {
            perror("fork failed");
            return 1;
        }

        if (pid2 == 0) {
            // 子进程2
            printf("Child 2: PID = %d, Parent PID = %d\n", getpid(), getppid());
            sleep(15); // 保持进程存活
            return 0;
        } else {
            // 父进程
            printf("Parent: PID = %d, Child1 PID = %d, Child2 PID = %d\n", getpid(), pid1, pid2);
            sleep(15); // 父进程也保持存活
        }
    }

    return 0;
}
2.2.1 源代码分析
复制代码
pid1 = fork(); // 创建第一个子进程
  • 第一个 fork() 调用后:

    如果返回 0 (子进程):子进程执行 if (pid1 == 0) 内的代码。

    如果返回非 0(父进程):父进程继续执行后续代码。

  • 此时,进程数量已经变成 2 个 (一个是原始父进程,另一个是 pid1 产生的子进程)。

    if (pid1 == 0) {
    // 子进程1
    printf("Child 1: PID = %d, Parent PID = %d\n", getpid(), getppid());
    sleep(15);
    return 0;
    }

  • 第一个子进程会直接 return,不会继续往下执行 fork() 相关的代码。

    pid2 = fork(); // 创建第二个子进程

  • 第二个 fork() 调用后:(前提条件是pid1>0,处于父进程)

如果返回 0 (子进程):子进程执行 if (pid2 == 0) 内的代码。

如果返回非 0(父进程):父进程继续执行后续代码。

此时,此时,进程数量从 2 个变成 3 个:

  1. 原始的父进程
  2. 第一个子进程(pid1
  3. 第二个子进程(pid2
2.2.2 源代码测试结果
复制代码
s:~/os_exp/exp2_process$ ./fork_two_children                       
Parent: PID = 1585110, Child1 PID = 1585111, Child2 PID = 1585112 
Child 1: PID = 1585111, Parent PID = 1585110 
Child 2: PID = 1585112, Parent PID = 1585110  

3. 打印进程树

3.1 tmux操作步骤

3.1.1 启动 tmux
复制代码
tmux

3.1.2 分屏操作(Ctrl+b是在告诉系统准备输入一个快捷键)
  • 垂直分屏(左右分屏)

    复制代码
    Ctrl + b,然后按 %
  • 水平分屏(上下分屏)

    复制代码
    Ctrl + b,然后按 "

3.1.3 切换窗口操作
  • 在不同的分屏间切换:

    复制代码
    Ctrl + b,然后按 方向键(←/→/↑/↓)

3.1.4 在新分屏中运行 pstree
  1. 在其中一个分屏中运行:

    复制代码
    ./fork_two_children
  2. 切换到另一个分屏,运行:

    复制代码
    pstree -p 父进程端口号

3.1.5 退出 tmux
  • 关闭当前窗口:

    复制代码
    exit

3.2 打印进程树结果

复制代码
~/os_exp/exp2_process$ pstree -p 1585110
fork_two_childr(1585110)─┬─fork_two_childr(1585111)
						 └─fork_two_childr(1585112)

4. 源码分析

4.1 Linux0.12中涉及的主要源码

复制代码
 struct task_struct {
 /* these are hardcoded - don't touch */
         long state;     /* -1 unrunnable, 0 runnable, >0 stopped */
         long counter;
         long priority;
         long signal;
         struct sigaction sigaction[32];
         long blocked;   /* bitmap of masked signals */
 /* various fields */
         int exit_code;
         unsigned long start_code,end_code,end_data,brk,start_stack;
         long pid,pgrp,session,leader;
         int     groups[NGROUPS];
         /* 
          * pointers to parent process, youngest child, younger sibling,
          * older sibling, respectively.  (p->father can be replaced with 
          * p->p_pptr->pid)
          */
         struct task_struct      *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
         unsigned short uid,euid,suid;
         unsigned short gid,egid,sgid;
         unsigned long timeout,alarm;
         long utime,stime,cutime,cstime,start_time;
         struct rlimit rlim[RLIM_NLIMITS]; 
         unsigned int flags;     /* per process flags, defined below */
         unsigned short used_math;
 /* file system info */
         int tty;                /* -1 if no tty, so it must be signed */
         unsigned short umask;
         struct m_inode * pwd;
         struct m_inode * root;
         struct m_inode * executable;
         struct m_inode * library;
         unsigned long close_on_exec;
         struct file * filp[NR_OPEN];
 /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
         struct desc_struct ldt[3];
 /* tss for this task */
         struct tss_struct tss;
 };
PCB通常包含的内容
进程描述信息 进程控制和管理信息 资源分配清单 处理机相关信息
进程标识符(PID) 进程当前状态 代码段指针 通用寄存器值
用户标识符(UID) 进程优先级 数据段指针 地址寄存器值
  • 在多道程序系统中,多个进程都要在CPU上运行,有时还要申请使用其他资源,由于资源的宝贵性,使得并非每个进程都能立即得到资源,从而导致进程之间的竞争(竞争是由两个以上进程以显式或隐式的方式共享资源而引起的状态)。

  • 一般情况下进程有三种状态,就绪(资源,CPU),运行(资源,CPU),阻塞(资源,CPU)。

  • Linux在每个进程的task_struct结构中,定义了state域来描述进程的调度状态,共有五种,定义如下:

    #define TASK_RUNNING 0 // 进程正在运行或已准备就绪。
    #define TASK_INTERRUPTIBLE 1 // 进程处于可中断等待状态。
    #define TASK_UNINTERRUPTIBLE 2 // 进程处于不可中断等待状态,主要用于I/O操作等待。
    #define TASK_ZOMBIE 3 // 进程处于僵死状态,已经停止运行,但父进程还没发信号。
    #define TASK_STOPPED 4 // 进程已停止。

4.2 结合源码分析实验代码

4.2.1 进程创建 (fork)

在实验代码中,调用fork()两次,创建了两个子进程。在Linux 0.12中,fork()的执行流程如下:

  • 系统调用入口fork()系统调用会触发sys_fork()函数(在kernel/system_call.s中定义),进而调用copy_process()(在kernel/fork.c中)。

  • 复制进程copy_process()会为新进程分配PCB,即task_struct结构,并从父进程的PCB复制大部分信息(如CPU寄存器、文件描述符等),且会为新进程分配唯一的pid(进程号),将子进程的state设置为TASK_RUNNING(就绪态),表示进程可以被调度执行,同时把新创建的进程加入调度队列,等待CPU调度。


4.2.2 具体进程状态转换

(1)初始状态

  • 父进程处于TASK_RUNNING状态,执行fork()创建Child 1进程。

(2)第一次fork()

  • Child 1被创建,其PCB信息从父进程复制,状态为TASK_RUNNING
  • Child 1pid被分配,ppid指向父进程。
  • 由于fork()的返回值不同:
    • 父进程继续执行,并进入下一步创建Child 2
    • Child 1进入自己的分支,执行printf然后调用sleep(15),使其进入TASK_INTERRUPTIBLE(睡眠状态)。

(3)第二次fork()

  • Child 2被创建,task_struct再复制一次。
  • Child 2pid被分配,ppid仍指向父进程。
  • 父进程继续执行,Child 2进入自己的分支执行printf然后sleep(15),进入TASK_INTERRUPTIBLE状态。

(4)运行状态

  • 进程调度器会根据counter字段和优先级选择进程运行。
  • **如果时间片耗尽或进程主动sleep(15),状态转换为TASK_INTERRUPTIBLE**,等待时间到达或信号唤醒。

(5)结束状态

  • Child 1Child 2完成sleep(15)后,调用return 0;,退出进程。
  • 在Linux 0.12中,进程退出时:
    • 进入TASK_ZOMBIE状态,等待父进程调用wait()回收资源。
    • 如果父进程没有及时wait(),子进程的task_struct仍然保留在进程表中(僵尸进程)。
  • 当父进程执行完sleep(15)后,也会结束,最终所有进程都退出,系统回收资源。

5. 实验过程中遇到的问题及解决方法

问题5.1 fork_two_children.c在运行过程中过快结束,来不及打印

解决方法:调用sleep() 函数让进程进入休眠(sleep)状态,即在指定的秒数内暂停执行,从而支持用户进行进程树的打印操作

问题5.2 单窗口在休眠状态下无法打印进程树

解决方法:学习了tmux 操作,通过打开水平窗口,并进行窗口的切换操作,从而通过 pstree -p 端口号 进行进程树的打印操作,详细操作方案可参考3.1内容

6.参考链接

进程概念------PCB详讲

【Linux系列】进程PCB控制管理详解

【linux系统】进程(进程PCB + 进程地址空间+进程控制)

相关推荐
还是鼠鼠几秒前
Node.js 跨域 CORS 简单请求与预检请求的介绍
运维·服务器·vscode·中间件·node.js·express
未来之窗软件服务2 小时前
数字人-局域网共用一个数字人平台-谷歌浏览器没有ssl配置
网络·网络协议·ssl·数字人
old_iron4 小时前
vim定位有问题的脚本/插件的一般方法
linux·编辑器·vim
爱知菜6 小时前
Windows安装Docker Desktop(WSL2模式)和Docker Pull网络问题解决
运维·docker·容器
做测试的小薄6 小时前
Nginx 命令大全:Linux 与 Windows 系统的全面解析
linux·自动化测试·windows·nginx·环境部署
影龙帝皖7 小时前
Linux网络之局域网yum仓库与apt的实现
linux·服务器·网络
月下雨(Moonlit Rain)7 小时前
Docker
运维·docker·容器
李詹7 小时前
如何解决DDoS攻击问题 ?—专业解决方案深度分析
网络·ddos
碎忆8 小时前
在VMware中安装虚拟机Ubuntu
linux·ubuntu
农民小飞侠8 小时前
ubuntu 安装pyllama教程
linux·python·ubuntu