allkillers 发表于 2016-03-21 09:21

关于fork进程中copy_files的疑问,求解答

在fork进程时,当COPY_FILES flag没有设置时,意味着继承之后的打开的文件信息需要另立门户,但是在如下代码中,仅仅是递增了file中f_count计数,并没有深度复制file机构中的信息。举个例子,fork后地子进程执行seek操作,同样也会导致父进程seek啊,不是很明白,求解惑。

struct files_struct *oldf, *newf;
struct file **old_fds, **new_fds;

......此处省略

old_fds = oldf->fd;
new_fds = newf->fd;

......此处省略

for (i = open_files; i != 0; i--) {
    struct file *f = *old_fds++;
    if (f)
      get_file(f);
      *new_fds++ = f;
}

nswcfd 发表于 2016-03-21 16:41

这是期望的行为吧?

nswcfd 发表于 2016-03-21 16:45

[~]$ cat file.c
#include <unistd.h>
#include <fcntl.h>
int main()
{
        int fd = open("file.txt", O_WRONLY | O_TRUNC | O_CREAT);
        write(fd, "1\n2\n3\n4\n", 8);
        if (fork() == 0) {
                lseek(fd, 0, 0);
                write(fd, "a\n", 2);
        } else {
                sleep(3);
                write(fd, "b\n", 2);
        }
}
[~]$ cat file.txt
a
b
3
4

allkillers 发表于 2016-03-22 15:02

看来书上写的有错啊。正确的理解应该是:
当设置CLONE_FILES,只是最顶层current->files->count ++就返回了,这就意味着子进程不仅共享父进程在fork之前已打开文件信息,还共享fork后父子进程之后打开的文件信息。
当不设置CLONE_FLAGS时,子进程仅共享fork之前父进程打开的文件。而fork之后父子进程打开的文件互不干扰。

貌似针对fork时的CLONE参数,基本可以有这样的结论:
无论CLONE参数设不设置,父子进程始终共享fork之前父进程的一些信息,而CLONE参数设置后,fork之后的行为就互不干扰了。
回复 3# nswcfd


   

nswcfd 发表于 2016-03-23 11:22

#define CLONE_FS        0x00000200        /* set if fs info 【shared】 between processes */
#define CLONE_FILES        0x00000400        /* set if open files 【shared】 between processes */

可以看到,某些CLONE_XXX的含义,通常是【不】做deep copy,跟字面意思正好反过来了。

但也不失绝对的,比如下面的行为跟字面就是一致的。
#define CLONE_THREAD        0x00010000        /* Same thread group? */
#define CLONE_NEWNS        0x00020000        /* New namespace group? */

allkillers 发表于 2016-03-24 15:42

恩,谢谢解答。
我这边在学习中断的时候,看到这样的描述,对如下红色部分有疑问,能帮忙答疑一下吗?是在不理解。


1.确定与中断或异常关联的向量i (0£i£255)。

2.籍由idtr寄存器从IDT表中读取第i项(在下面的描述中,我们假定该IDT表项中包含的是一个中断门或一个陷阱门)。

3.从gdtr寄存器获得GDT的基地址,并在GDT表中查找,以读取IDT表项中的选择符所标识的段描述符。这个描述符指定中断或异常处理程序所在段的基地址。



4.确信中断是由授权的(中断)发生源发出的。首先将当前特权级CPL(存放在cs寄存器的低两位)与段描述符(存放在GDT中)的描述符特权级DPL比较,如果CPL小于DPL,就产生一个“通用保护”异常,因为中断处理程序的特权不能低于引起中断的程序的特权。对于编程异常,则做进一步的安全检查:比较CPL与处于IDT中的门描述符的DPL,如果DPL小于CPL,就产生一个“通用保护”异常。这最后一个检查可以避免用户应用程序访问特殊的陷阱门或中断门。



5.检查是否发生了特权级的变化,也就是说, CPL是否不同于所选择的段描述符的DPL。如果是,控制单元必须开始使用与新的特权级相关的栈。通过执行以下步骤来做到这点:
a.读tr寄存器,以访问运行进程的TSS段。
b.用与新特权级相关的栈段和栈指针的正确值装载ss和esp寄存器。这些值可以在TSS中找到(参见第三章的“任务状态段”一节)。
c.在新的栈中保存ss和esp以前的值,这些值定义了与旧特权级相关的栈的逻辑地址。// 到这里ss和esp寄存器已经是新特权的栈了,怎么知道以前的esp值?



    6.如果故障已发生,用引起异常的指令地址装载cs和eip寄存器,从而使得这条指令能再次被执行。

    7.在栈中保存eflag、cs及eip的内容。 // 同理这里cs还是没变化过,可以保存,但经过上述一系列步骤,eip也在随着变化啊,eip已经不是中断前的eip了啊

8.如果异常产生了一个硬错误码,则将它保存在栈中。

9.装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量域。这些值给出了中断或者异常处理程序的第一条指令的逻辑地址。//在这之前还一直在用户态,但栈已经切换到内核,之前保存现场的操作是在用户特权级下操作内核地址空间,这样没问题吗?


回复 4# allkillers


   

allkillers 发表于 2016-03-24 15:43

恩,谢谢解答。
我这边在学习中断的时候,看到这样的描述,对如下红色部分有疑问,能帮忙答疑一下吗?是在不理解。


1.确定与中断或异常关联的向量i (0£i£255)。

2.籍由idtr寄存器从IDT表中读取第i项(在下面的描述中,我们假定该IDT表项中包含的是一个中断门或一个陷阱门)。

3.从gdtr寄存器获得GDT的基地址,并在GDT表中查找,以读取IDT表项中的选择符所标识的段描述符。这个描述符指定中断或异常处理程序所在段的基地址。



4.确信中断是由授权的(中断)发生源发出的。首先将当前特权级CPL(存放在cs寄存器的低两位)与段描述符(存放在GDT中)的描述符特权级DPL比较,如果CPL小于DPL,就产生一个“通用保护”异常,因为中断处理程序的特权不能低于引起中断的程序的特权。对于编程异常,则做进一步的安全检查:比较CPL与处于IDT中的门描述符的DPL,如果DPL小于CPL,就产生一个“通用保护”异常。这最后一个检查可以避免用户应用程序访问特殊的陷阱门或中断门。



5.检查是否发生了特权级的变化,也就是说, CPL是否不同于所选择的段描述符的DPL。如果是,控制单元必须开始使用与新的特权级相关的栈。通过执行以下步骤来做到这点:
a.读tr寄存器,以访问运行进程的TSS段。
b.用与新特权级相关的栈段和栈指针的正确值装载ss和esp寄存器。这些值可以在TSS中找到(参见第三章的“任务状态段”一节)。
c.在新的栈中保存ss和esp以前的值,这些值定义了与旧特权级相关的栈的逻辑地址。// 到这里ss和esp寄存器已经是新特权的栈了,怎么知道以前的esp值?



    6.如果故障已发生,用引起异常的指令地址装载cs和eip寄存器,从而使得这条指令能再次被执行。

    7.在栈中保存eflag、cs及eip的内容。 // 同理这里cs还是没变化过,可以保存,但经过上述一系列步骤,eip也在随着变化啊,eip已经不是中断前的eip了啊

8.如果异常产生了一个硬错误码,则将它保存在栈中。

9.装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量域。这些值给出了中断或者异常处理程序的第一条指令的逻辑地址。//在这之前还一直在用户态,但栈已经切换到内核,之前保存现场的操作是在用户特权级下操作内核地址空间,这样没问题吗?



    回复 5# nswcfd


   

nswcfd 发表于 2016-03-25 11:01

这是ULK里关于异常或中断的描述吧,看英文版的更清楚一些。

问题9,英文版写的很清楚,这里的cs/ip来自IDT中的Gate Descriptor(也就是第2步读的东西)

问题5c,cpu当然知道了……甚至可以假设到这一步cs/ip还没有被改变呢(直到第9步才发生变化)。

nswcfd 发表于 2016-03-25 11:02

PS,如果对细节感兴趣的话,可以看一下邓志的《x86/x64体系探索及编程》。
页: [1]
查看完整版本: 关于fork进程中copy_files的疑问,求解答