代码之家  ›  专栏  ›  技术社区  ›  NosPix

处理多个fork()s

  •  1
  • NosPix  · 技术社区  · 7 年前

    对于一个练习,我必须编写一个程序来并行比较两个文件。 并行度由用户通过-j参数决定。

    如:
    /myprog-j 8文件1文件2

    意味着将创建8个进程,每个进程将比较两个文件中的1/8。

    假设我们有3个文件,其中2个相等(file1=file2)。
    当程序比较两个不同的文件时,一切都很好(或者至少错误是隐藏的)。 当我比较两个相同的文件时,大约每四个文件中就有一个,程序会说“file1 file2 difference”,这显然是错误的。

    有人能帮助我理解为什么以及如何修复它吗? 谢谢您!

    #include <...>
    
    int cmp(int, int, int, int);
    
    int main(int argc, char *argv[]){
        int len, status;
        int fd1, fd2;
        int term;
        int i, j;
        int opt, num;
        int start, stop;
    
    //[...] Various checks on files length and getopt()
    
        num = atoi(optarg);
        int pid[num];
    
        for(i = 0; i < num; i++){
            pid[i] = fork();
            if(pid[i] == 0){
                start = (len/num)*i;
                stop = ((len/num)*(i+1))-1;
                if(cmp(fd1, fd2, start, stop)) abort();
                else exit(EXIT_SUCCESS);    
            }else if(pid[i] < 0){
                perror(NULL);
                exit(EXIT_FAILURE);
            }
        }
    
    for(i = 0; i < num; i++){
        term = wait(&status);
        if(WIFSIGNALED(status)){
            //when the error occours start or stop result gives a rondom high number like 1835627636
            printf("PID-> %d START %d STOP %d\n", term, start, stop);
            fprintf(stderr, "%s\n", "file1 file2 differ");
            for(j = 0; j < num; j++){
                if(pid[j] != term){
                    printf("KILL %d\n", j);
                    kill(pid[j], 0);
                }
            }
            exit(EXIT_FAILURE);
        }
    }
    exit(EXIT_SUCCESS);
    
    
    }
    
    int cmp(int fd1, int fd2, int start, int stop){
        char buf1, buf2;
        if(start > stop) return 0;
        else{
            fsync(fd1);
            fsync(fd2);
            lseek(fd1, start, SEEK_SET);
            read(fd1, &buf1, sizeof(char));
            lseek(fd2, start, SEEK_SET);
            read(fd2, &buf2, sizeof(char));
            if(buf1 != buf2) return 1;
            else cmp(fd1, fd2, start+1, stop);
         }
    }
    
    2 回复  |  直到 7 年前
        1
  •  1
  •   Craig Estey    7 年前

    cmp else

    else cmp(fd1, fd2, start+1, stop);
    

    else return cmp(fd1, fd2, start+1, stop);
    

    另外,在 main , start/stop 只设置在 小孩 进程,因此它们在父进程中无效。此外,即使它们在父级中有效,它们也只能设置为 最后的 孩子分叉了。

    此外,也不能保证 wait 将返回PIDS 整齐 . (例如,它可能会返回 pid[3] 之前 pid[2] . 因此,如果不应该的话,您可以尝试终止进程。 waitpid 相反。或者只是做:

    int retval = EXIT_SUCCESS;
    while (1) {
        term = wait(&status);
        if (term < 0)
            break;
        if (WIFSIGNALED(status)) {
            printf(...);
            retval = EXIT_FAILURE;
        }
    }
    exit(retval);
    

    而且, 去除 这个 kill 因为它不是真正需要的,而且更容易引起问题(也就是说,它是一个bug的来源)。哎呀!我刚注意到你正在发送一个 杀死 信号值为零。这在很大程度上是不允许的。


    更新:

    通过在同一个文件上启动程序并打印比较的字符,程序将打印相同的字符,但最后两个字符按顺序颠倒,因此比较buf1!=buf2为真,cmp返回1。这可能是种族状况吗?

    是的,没错。因为每个子级使用相同的文件描述符, lseek 一个孩子会影响其他孩子的档案位置。

    来自 fork 手册页:

    子级继承父级打开的文件描述符集的副本。子级中的每个文件描述符都引用相同的打开 文件描述(见打开(2))作为相应的文件描述符 在父级中。这意味着两个文件描述符共享打开 文件状态标志, 文件偏移量 和信号驱动的I/O属性

    以下是关于共享文件偏移量对 dup 手册页:

    成功返回后,可以使用旧的和新的文件描述符 可互换。它们引用相同的打开文件描述(请参见 打开(2)),共享文件偏移量和文件状态标志; 例如, 如果文件偏移量是通过在其中一个文件上使用lseek(2)修改的 描述符中,另一个的偏移量也会改变。 (参见fcntl(2)中的f_setown和f_setsig说明)。

    为了解决这个问题,让每个孩子都做它自己的/独特的 open 两个文件中的一个。然后,他们 不会 共享文件偏移量 比赛条件。额外开销 open/close 将是最小的。

    还有几点…

    信号通常是为特殊/意外情况(如分段故障等)保留的。因此,使用 abort 在子通信中,不匹配可以/应该用非零退出代码替换。

    这是更清洁和提供更大程度的灵活性。 EXIT_SUCCESS/EXIT_FAILURE 是一组相对新的定义。做的时候 exit(code) ,允许孩子返回 任何 7位错误代码(包括0-127)。参见手册页 化学机械抛光 rsync 举个例子。

    在这里,您可以使用您希望的任何子错误代码约定,例如:0=匹配、1=不匹配、2=一个文件的短读、3=I/O错误等。

    正如阿加努指出的,使 化学机械抛光 函数递归将 非常 效率低下,导致堆栈崩溃。大多数堆栈默认为~8MB,因此这限制了可以处理的文件的大小。考虑8个流程的例子。如果文件大小大于64MB,即使文件大小相等,也会溢出堆栈并得到一个segfault。

    另外,做一个 read 对于一个 单一的 字节非常慢,并且完全否定了并行进程获得的任何速度增益。

    因此,即使您将工作拆分为多个流程,每个子流程仍然必须循环处理其自己的职责部分中较小的部分。

    当读取最佳缓冲区大小时,并不总是最大的。对于某些文件系统(例如 ext4 )建议的大小IIRC为64KB。

    我还建议使用一个自定义结构来跟踪 启动/停止 位置和各种其他每个子数据。


    这里是您的代码的重构版本,说明了其中的大部分内容。它没有经过测试,仍然有点粗糙,但它应该能帮助您进一步(请原谅这种无偿的风格清理)。

    注意/检查事项:

    不同长度的文件不需要进行比较。他们可以 从未 匹配。此处 化学机械抛光 假设文件的长度相等。

    应将进程数裁剪为文件大小(例如,如果您有8个进程,文件长度为4,则应将进程数裁剪为4)。

    范围/限值/尺寸计算应加倍检查,因为可能存在“一次关闭”错误。

    另外,一定要使用 off_t (确保64位值正确 #define 设置在任何之前 #include 指令)允许程序正确处理文件>2GB

    #include <...>
    
    char *file1
    char *file2;
    
    typedef struct pidctl {
        int chunknum;
        off_t start;
        off_t stop;
    } pidctl_t;
    
    #define CHUNKSIZE       (64 * 1024)
    
    int retcode = EXIT_SUCCESS;
    
    int cmp(pidctl_t *ctl);
    
    int
    main(int argc, char *argv[])
    {
        off_t len;
        int status;
        int fd1;
        int fd2;
        int term;
        int i,
         j;
        int opt,
         num;
        int start,
         stop;
    
        //[...] Various checks on files length and getopt()
    
        num = atoi(optarg);
    
        // process count should be clipped to number of bytes in file
        if (num > filesize)
            num = filesize;
    
        pidctl_t pidlist[num];
    
        for (i = 0; i < num; i++) {
            ctl = &pidlist[i];
    
            ctl->chunknum = i;
            ctl->start = (len / num) * i;
            ctl->stop = ((len / num) * (i + 1)) - 1;
    
            // the last segment must be clipped to the file size
            if (ctl->stop >= len)
                ctl->stop = len - 1;
    
            ctl->pid = fork();
    
            if (ctl->pid == 0)
                exit(cmp(ctl));
    
            if (ctl->pid < 0) {
                perror(NULL);
                exit(EXIT_FAILURE);
            }
        }
    
        while (1) {
            term = wait(&status);
            if (term < 0)
                break;
    
            for (i = 0; i < num; i++) {
                ctl = &pidlist[i];
                if (term == ctl->pid) {
                    if (WIFSIGNALED(status)) {
                        printf("PID-> %d START %lld STOP %lld -- signal %d\n",
                            ctl->pid, ctl->start, ctl->stop, WTERMSIG(status));
                        retcode = 2;
                    }
    
                    if (WIFEXITED(status)) {
                        int code = WEXITSTATUS(status);
                        printf("PID-> %d START %lld STOP %lld -- code %d -- %s\n",
                            ctl->pid, ctl->start, ctl->stop,
                            code,code ? "FAIL" : "OK");
                        if (code)
                            retcode = 1;
                    }
    
                    continue;
                }
            }
        }
    
        exit(retcode);
    }
    
    int
    cmp(pidctl_t *ctl)
    {
        int fd1 = -1;
        char buf1[CHUNKSIZE];
        int fd2 = -1;
        char buf2[CHUNKSIZE];
        off_t pos;
        off_t remain;
        int rlen1;
        int rlen2;
        int xlen;
        int code = 0;
    
        do {
            if (ctl->start > ctl->stop)
                break;
    
            fd1 = open(file1,O_RDONLY);
            if (fd1 < 0) {
                code = 2;
                break;
            }
            pos = lseek(fd1, ctl->start, SEEK_SET);
            if (pos != ctl->start) {
                code = 3;
                break;
            }
    
            fd2 = open(file2,O_RDONLY);
            if (fd2 < 0) {
                code = 2;
                break;
            }
            pos = lseek(fd2, ctl->start, SEEK_SET);
            if (pos != ctl->start) {
                code = 3;
                break;
            }
    
            remain = (ctl->stop - ctl->start) + 1;
            for (;  remain > 0;  remain -= xlen)
                xlen = CHUNKSIZE;
                if (xlen > remain)
                    xlen = remain;
    
                rlen1 = read(fd1, buf1, xlen);
                if (rlen1 != xlen) {
                    code = 4;
                    break;
                }
    
                rlen2 = read(fd2, buf2, xlen);
                if (rlen2 != xlen) {
                    code = 5;
                    break;
                }
    
                if (memcmp(buf1,buf2,xlen) != 0) {
                    code = 1;
                    break;
                }
            }
        } while (0);
    
        if (fd1 >= 0)
            close(fd1);
        if (fd2 >= 0)
            close(fd2);
    
        return code;
    }
    
        2
  •  1
  •   Aganju    7 年前

    如果没有指定FD1和FD2的代码,这是一种猜测,但我认为 cmp 函数盲目假设 fsync , lseek read 会有用的, 不检查它们的返回值 . 如果其中一个失败(例如,因为文件被锁定),那么相应的缓冲区将不匹配,这就是它要报告的内容。

    注意这一点 化学机械抛光 递归它非常低效,完全多余。它可以工作,但它会产生一个一英里长的堆栈,甚至可能会使较大文件的程序崩溃。一段时间或一段时间就足够了。