使用 svc 调用来实现 fork()。

4

我实现了一个系统调用宏来调用软件中断。对于许多系统调用,这都很有效。然而,对于 fork() 一点也不行。父进程和子进程的返回 pid 是相同的。代码片段如下:

#define SYSCALL0(N) ({                                          \
    register int ip asm("ip") = N;                              \
    register int r0 asm("r0");                                  \
    asm volatile("swi 0x80" : "=r"(r0) : "r"(ip) : "memory");   \
    r0;                                                         \
})

int main(int argc, char * argv[]) {
   NSLog(@"--beginning of program\n");
    int counter = 0;
    pid_t pid = SYSCALL0(2);
    if (pid == 0) {
        NSLog(@"pid = %d", getpid());
        // child process
        for (int i = 0; i < 3; ++i)
            NSLog(@"child process: counter=%d\n", ++counter);
    }
    else if (pid > 0)  {
        NSLog(@"pid = %d", getpid());
        // parent process
        for (int i = 0; i < 3; ++i)
            NSLog(@"parent process: counter=%d\n", ++counter);
    }
    else {
        // fork failed
        NSLog(@"fork() failed!\n");
        return 1;
    }
    NSLog(@"--end of program--\n");
}

输出:

2015-10-11 21:29:43.666 Training[2564:907] --beginning of program
2015-10-11 21:29:43.669 Training[2564:907] pid = 2650
2015-10-11 21:29:43.670 Training[2564:907] parent process: counter=1
2015-10-11 21:29:43.670 Training[2564:907] parent process: counter=2
2015-10-11 21:29:43.669 Training[2564:907] pid = 2650
2015-10-11 21:29:43.671 Training[2564:907] parent process: counter=3
2015-10-11 21:29:43.671 Training[2564:907] --end of program--
2015-10-11 21:29:43.671 Training[2564:907] parent process: counter=1
2015-10-11 21:29:43.672 Training[2564:907] parent process: counter=2
2015-10-11 21:29:43.673 Training[2564:907] parent process: counter=3
2015-10-11 21:29:43.674 Training[2564:907] --end of program--

被测试的环境是一个越狱的 iOS 系统(非越狱系统无法运行),运行在 armv7 处理器上。我认为可能没有对 swi 调用的返回值做足够的处理,因此无法返回 0 来指示子进程。我错过了什么?如何使其正常工作?


你有什么问题? - Lorenzo
2
抱歉,如果不清楚,我正在编辑问题。问题是“如何使它正常工作”。 - Krypton
2个回答

2
您不应该这样做系统调用。用户态封装系统调用的工作比您刚刚发现的要复杂得多。因此,要么找到操作系统的源代码,弄清楚如何封装系统调用并执行相同操作,要么直接使用已经为您方便地提供的封装器。
我很确定iOS仍然是按照BSD方式进行系统调用。我碰巧知道fork使用了两个返回值syscall接口,一个寄存器包含进程是否为子进程,另一个寄存器包含返回值。
另外,您的封装器可能没有正确处理错误处理(通常通过特殊处理标志来表示)。至少一些操作系统(不知道iOS是否是其中之一)知道哪些寄存器在syscall封装器中被保存,而不会费力保存它们,因此像这样内联系统调用可能会破坏您的寄存器。

问题是我似乎找不到任何源代码,所以我通过查看此页面https://www.theiphonewiki.com/wiki/Kernel_Syscalls 自己实现了它。 - Krypton
如果你在网上搜索一下,可能会找到一些旧的达尔文源代码,这应该足够接近了。但是,是的,你不应该自己这样做。使用操作系统提供的适当封装的系统调用有什么问题吗? - Art
我找到了它的达尔文代码。希望它能够工作,让我们看看。 - Krypton
代码在这里 https://github.com/darwin-on-arm/xnu/blob/master/libsyscall/custom/__fork.s,但我不确定如何将 MI_GET_ADDRESS(r3, __current_pid) 转换为内联汇编代码。 - Krypton

2

我弄清楚了。原来在服务调用之后,r1被设置为指示器,以确定后续代码是子进程还是父进程。代码片段已更改如下:

#include <dlfcn.h>
int svc_fork() {
    register int r3 asm("r3") = (int)dlsym(RTLD_DEFAULT, "_current_pid");
    register int r12 asm("r12") = 2;
    register int r1 asm("r1") = 1;
    register int r0 asm("r0");
    asm volatile("swi   0x80                        \n"
                 "cmp   r1, #0                      \n"
                 "beq   Lparent                     \n"
                 "mov   r0, #0                      \n"
                 "str r0, [r3]                      \n"
                 "Lparent:                          \n"
                 : "=r"(r0)
                 : "r"(r1), "r"(r12), "r"(r3)
                 : "memory");
    return r0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接