[OS] 项目三-1-proc.c: fork()探求

cpp 复制代码
// Create a new process, copy the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }
  
  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  np->sz = p->sz;

  // copy saved user registers.
  *(np->trapframe) = *(p->trapframe);

  // Cause fork to return 0 in the child.
  np->trapframe->a0 = 0;

  // increment reference counts on open file descriptors.
  // 这一块是做什么的?
  for(i = 0; i < NOFILE; i++)
    if(p->ofile[i])
      np->ofile[i] = filedup(p->ofile[i]);
  np->cwd = idup(p->cwd);

  safestrcpy(np->name, p->name, sizeof(p->name));

  pid = np->pid;

  release(&np->lock);

  acquire(&wait_lock);
  np->parent = p;
  release(&wait_lock);

  acquire(&np->lock);
  np->state = RUNNABLE;
  release(&np->lock);

  return pid;
}

功能解释

  1. 文件描述符的复制

    • for(i = 0; i < NOFILE; i++) 逐个遍历父进程的文件描述符表(p->ofile),NOFILE 是文件描述符的最大数量。
    • if(p->ofile[i]) 检查当前文件描述符(p->ofile[i])是否被父进程打开,只有打开的文件才需要复制。
    • np->ofile[i] = filedup(p->ofile[i]); 将父进程的每个有效文件描述符(p->ofile[i])复制到子进程(np->ofile[i])中。filedup 函数的作用是增加文件引用计数,以便文件资源能被父子进程共享而不冲突。
  2. 工作目录的复制

    • np->cwd = idup(p->cwd); 将父进程的当前工作目录(p->cwd)复制给子进程,并增加目录的引用计数。
    • idup 函数的作用是增加目录的引用计数,以便父子进程可以共享同一个工作目录,同时避免一个进程修改目录状态影响到另一个进程。

通俗解释

当一个进程调用 fork() 时,会创建一个子进程,该子进程需要与父进程共享文件资源(例如打开的文件、工作目录等)。为了实现这一点,fork() 需要将父进程中所有打开的文件描述符都复制到子进程中。

  • 每个文件描述符都对应一个文件对象,当我们复制文件描述符时,其实并不是创建一个新的文件对象,而是增加文件对象的引用计数,让父子进程可以同时使用它。
  • 工作目录也是类似的,父子进程共享相同的目录对象,通过引用计数来管理资源的共享。
cpp 复制代码
// Create a new process, copy the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }
  
  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  np->sz = p->sz;

  // copy saved user registers.
  *(np->trapframe) = *(p->trapframe);

  // Cause fork to return 0 in the child.
  np->trapframe->a0 = 0;

  // increment reference counts on open file descriptors.
  for(i = 0; i < NOFILE; i++)
    if(p->ofile[i])
      np->ofile[i] = filedup(p->ofile[i]);
  np->cwd = idup(p->cwd);

  safestrcpy(np->name, p->name, sizeof(p->name));

  pid = np->pid;

  release(&np->lock);

  acquire(&wait_lock);
  np->parent = p;
  release(&wait_lock);

  acquire(&np->lock);
  np->state = RUNNABLE;
  //TODO
  for(int i=0; i < VMASIZE; i++) 
  {
    memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));
    if(p->vma[i].f) filedup(p->vma[i].f);
  }
  //TODO end
  release(&np->lock);

  return pid;
}

解释新增代码

在原始的 fork() 实现中,只考虑了复制内存页和文件描述符,但当进程增加了 VMA 功能(比如 mmap 支持),就需要在 fork() 时把父进程的 VMA 结构一并复制到子进程。这里的 TODO 部分完成了这个目的。

cpp 复制代码
for(int i=0; i < VMASIZE; i++) 
{
  memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));
  if(p->vma[i].f) filedup(p->vma[i].f);
}

新增代码的详细解释

  1. 遍历和复制 VMA

    • for(int i=0; i < VMASIZE; i++):遍历 VMA 数组,VMASIZE 是进程支持的最大 VMA 数量。
    • memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));:将父进程 p 的第 i 个 VMA 结构复制到子进程 np 的相应位置。
    • 这样做是为了让子进程拥有与父进程相同的 VMA 设置,使得 mmap 相关的映射关系可以在子进程中继续有效。
  2. 增加 VMA 文件的引用计数

    • if(p->vma[i].f) filedup(p->vma[i].f);:如果父进程的 VMA 关联了一个文件(p->vma[i].f 非空),则通过 filedup 函数增加文件的引用计数。
    • 这样做可以确保子进程的 VMA 正确引用到同一个文件,并且当父子进程使用映射时,共享同一个文件对象,防止因为文件关闭导致子进程的映射失效。

1. -> 操作符

  • 含义->结构体指针访问操作符,用于访问指针指向的结构体中的成员。
  • 使用方式指针变量->成员名
  • 例子 :在 np->vma[i] 中,np 是一个指向 proc 结构体的指针,vmaproc 结构体中的一个成员。np->vma[i] 表示访问 np 指向的 proc 结构体的 vma 数组的第 i 个元素。

2. . 操作符

  • 含义.结构体成员访问操作符,用于访问结构体变量中的成员。
  • 使用方式结构体变量.成员名
  • 例子p->vma[i].f 中,p->vma[i] 是一个 vma 结构体,.f 用于访问 vma 结构体中的 f 成员(假设 fvma 结构体中的一个文件指针)。

3. & 操作符

  • 含义& 是取地址符号,用于获取变量的地址。
  • 使用方式&变量名
  • 例子 :在 &p->vma[i] 中,p->vma[i] 是一个 vma 结构体,&p->vma[i] 表示获取该结构体的地址。

4. memmove 函数

  • 功能memmove 用于内存拷贝,将源地址的内容复制到目标地址,可以安全地处理重叠区域。
  • 原型void *memmove(void *dest, const void *src, size_t n);
    • dest:目标地址,即将数据复制到的地址。
    • src:源地址,即从哪里复制数据。
    • n:要复制的字节数。
  • 例子memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i])); 表示将父进程的第 ivma 结构体数据复制到子进程对应的 vma 结构体中。

5. filedup 函数

  • 功能filedup 用于增加文件的引用计数,让多个进程能够共享同一个文件对象,而不需要各自打开新的文件。
  • 使用filedup(p->vma[i].f) 中,如果 p->vma[i].f 是一个指向文件的指针,filedup 会让 f 文件的引用计数加 1,确保文件在父子进程中可以同时使用。

6. [i]:数组索引

  • 含义[i] 是数组索引,用于访问数组中第 i 个元素。
  • 例子p->vma[i] 中,p->vma 是一个数组,[i] 表示数组中的第 i 个元素。
cpp 复制代码
for(int i=0; i < VMASIZE; i++) 
{
  memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));
  if(p->vma[i].f) filedup(p->vma[i].f);
}
  • for(int i=0; i < VMASIZE; i++):这是一个循环,从 i=0i=VMASIZE-1,每次循环内会执行复制 VMA 的操作。

  • memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));

    • &p->vma[i] 表示获取父进程 pvma[i] 的地址。
    • &np->vma[i] 表示获取子进程 npvma[i] 的地址。
    • sizeof(p->vma[i]) 表示要复制的数据大小,即每个 vma 结构体的大小。
    • memmove 会将 p->vma[i] 的数据复制到 np->vma[i] 中。
  • if(p->vma[i].f) filedup(p->vma[i].f);

    • p->vma[i].f 是父进程 vma[i] 结构体中的文件指针 f
    • if(p->vma[i].f) 检查 f 是否为非空,如果 f 不为空,说明 vma[i] 关联了一个文件。
    • filedup(p->vma[i].f); 增加该文件的引用计数,确保子进程也可以安全使用这个文件。

memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));这一行为啥不是比如说直接用等号或者copy啥的

这是因为 memmove 更适合在 C 语言中处理结构体的内存复制。直接使用等号 = 或某些 copy 函数(如 strcpy)可能无法正确完成结构体的深层次复制。以下是一些原因:

1. memmove 适合复制结构体数据块

memmove 是一种低层次的内存复制函数,可以直接在内存级别将一个区域的数据复制到另一个区域。它能够高效地复制任意类型的数据结构,包括数组、结构体、或者其他复杂类型。相比于简单的赋值操作 =memmove 可以确保:

  • 深层次复制 :即使结构体中包含指针,memmove 也会将指针的内容(而不是地址)复制到目标区域。
  • 大小灵活 :通过 sizeof 可以精确控制复制的字节数。

2. 为什么不能直接使用 =

=C 语言中只能对基础类型 或指针进行赋值,不能直接用于结构体数组或复杂类型的深层次复制。直接使用 = 可能会导致不完全的复制或错误的指针引用。下面是一些具体原因:

  • 结构体复制 :在 C 中,如果两个结构体指针 aba = b 只会复制指针本身,即它们将指向同一个地址,而不是复制指向的数据本身。这会导致父子进程的 vma 数组引用同一个内存区域,这并不是我们期望的行为。
  • 指针成员问题 :如果结构体中有指针成员(如文件指针 f),直接赋值会导致父子结构体的指针指向同一块内存,而不是复制数据。

3. memmovememcpy 的选择

memmovememcpy 的功能类似,但 memmove 能更安全地处理重叠内存区域 ,保证复制过程中的数据不会被破坏。因此,在一些情况下,memmove 是更安全的选择(尽管在这里没有明显的重叠问题)。

cpp 复制代码
memmove(&np->vma[i], &p->vma[i], sizeof(p->vma[i]));
相关推荐
A懿轩A6 分钟前
C/C++ 数据结构与算法【树和森林】 树和森林 详细解析【日常学习,考研必备】带图+详细代码
c语言·c++·考研·数据结构与算法·树和森林
C语言编程小刘 132 分钟前
C语言期末复习1.1
c语言·算法·leetcode
Bucai_不才1 小时前
【C++】初识C++之C语言加入光荣的进化(下)
c语言·c++·面向对象编程
A懿轩A1 小时前
C/C++ 数据结构与算法【哈夫曼树】 哈夫曼树详细解析【日常学习,考研必备】带图+详细代码
c语言·c++·学习·算法·哈夫曼树·王卓
w_outlier1 小时前
cookie__HTTPS
c++·网络协议·http·https
编码小哥1 小时前
C++线程同步和互斥
开发语言·c++
一入程序无退路3 小时前
c语言传参数路径太长,导致无法获取参数
linux·c语言·数据库
打鱼又晒网3 小时前
Linux网络 | 网络计算器客户端实现与Json的安装以及使用
linux·c++·网络协议·计算机网络
lili-felicity3 小时前
指针与数组:深入C语言的内存操作艺术
c语言·开发语言·数据结构·算法·青少年编程·c#
DARLING Zero two♡4 小时前
【优选算法】Sliding-Chakra:滑动窗口的算法流(上)
java·开发语言·数据结构·c++·算法