处理多个fork()

时间:2018-07-13 00:50:39

标签: c fork

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

例如:
./ myprog -j 8 file1 file2

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

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

有人可以帮助我了解为什么以及如何解决它吗? 谢谢!

#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 个答案:

答案 0 :(得分:1)

没有分配fd1和fd2的代码,这只是一个猜测,但是我认为您的cmp函数盲目地假设fsynclseekread可以工作,不检查其返回值。如果任何一个失败(例如,由于文件已锁定),则相应的缓冲区当然将不匹配,这就是它报告的内容。

请注意,使cmp递归非常无效且完全多余。它可以工作,但是会产生一英里长的堆栈,甚至可能使您为大文件编写程序时崩溃。一个简单的时间或时间就足够了。

答案 1 :(得分:1)

cmp可能返回随机值(即未定义的行为),因为最后的else是:

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

应该是:

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

此外,在main中,start/stop仅在进程中设置,因此它们在父进程中无效。另外,即使它们在父级中有效,也只能将其设置为 last 分支的子代的值。

此外,不能保证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,因为它并不是真正需要的,并且更可能引起问题(即,它是您的一个错误的来源)。糟糕!我刚刚注意到您正在发送信号值为零的kill。这基本上是无人操作。


更新:

  

通过在同一文件上启动程序并打印比较的字符,程序将打印相同的字符,但最后两个字符顺序相反,因此比较buf1!= buf2为true,cmp返回1。是种族问题吗?

是的。由于每个孩子都使用相同的文件描述符,因此一个孩子中的lseek将影响其他所有孩子的文件位置。

fork联机帮助页中:

  

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

关于dup联机帮助页上共享文件偏移量的效果,这里有更多内容:

  

成功返回后,可以使用旧文件描述符和新文件描述符          可以互换它们引用相同的打开文件描述(请参阅          open(2)),从而共享文件偏移量和文件状态标志; 例如,          如果通过在文件之一上使用lseek(2)修改了文件偏移量          描述符,则偏移量也会更改。             (请参阅fcntl(2)中对F_SETOWN和F_SETSIG的描述)。

要解决此问题,请让每个孩子做两个文件中自己的/唯一的open。然后,他们不会共享文件偏移,并且不会出现没有竞争条件。额外的open/close的额外开销将很小。

还有几点...

通常为异常/意外情况(例如分段故障等)保留信号。因此,可以/应该用孩子中的abort来传达不匹配信息,可以/应该用非零退出代码代替。

这更加清洁,并提供了更大的灵活性。 EXIT_SUCCESS/EXIT_FAILURE是一组相对较新的定义。执行exit(code)时,允许孩子返回任何 7位错误代码(即0-127(含0-127))。请以cmprsync的手册页为例。

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

正如Aganju所指出的那样,使cmp函数递归将非常效率低下并使堆栈崩溃。大多数堆栈默认为〜8MB,因此这限制了您可以处理的文件大小。考虑您的8个流程示例。如果文件大小大于64MB,即使文件相等,也会溢出堆栈并出现段错误。

此外,对一个单个字节执行read非常慢,并且完全抵消了并行处理所获得的任何速度提升。

因此,即使您将工作分散在多个流程之间,每个子流程仍将不得不遍历其职责范围内的较小块

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

我还建议使用一个自定义结构,该结构可以跟踪start/stop的位置和其他各个子数据。


这是代码的重构版本,它说明了很多内容。它未经测试,但仍然有些粗糙,但是它应该可以帮助您进一步使用[请原谅免费的样式清理]。

注意/检查的事情

不同长度的文件不需要比较。他们从不匹配。这里的cmp假定文件的长度相等。

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

应该仔细检查范围/限制/大小的计算,因为可能会出现“一一偏离”的错误。

此外,请确保使用off_t(它保证在任何#define伪指令之前设置正确的#include的64位值)以允许程序正确处理文件> 2GB < / p>

#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;
}