c - 处理多个 fork()

标签 c fork

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

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

表示将创建 8 个进程,每个进程将比较两个文件的 1/8。

假设我们有 3 个文件,其中 2 个是相等的(file1 = file2)。
当程序比较两个不同的文件时,一切都很好(或者至少隐藏了错误)。
当我去比较两个相同的文件时,大约四分之一,程序说“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);
     }
}

最佳答案

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

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

它应该是:
else return cmp(fd1, fd2, start+1, stop);

此外,在 main , start/stop仅在子进程中设置,因此在父进程中无效。此外,即使它们在父级中有效,它们也只会设置为最后一个 fork 的子级的值。

此外,不保证 wait将按顺序返回 pid。 (例如,它可能会在 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信号值为零。这在很大程度上是无操作的。

更新:

By launching the program on the same file and printing the characters that are compared, the program prints the same characters except the last two which are reversed in order and therefore the comparison buf1!=buf2 is true and cmp return 1. Could it be a race condition prolem?



对,就是这样。因为每个 child 使用相同的文件描述符,lseek在一个 child 中会影响所有其他 child 的文件位置。

来自 fork手册页:

The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent. This means that the two file descriptors share open file status flags, file offset, and signal-driven I/O attributes



这里有更多关于从 dup 共享文件偏移量的效果。手册页:

After a successful return, the old and new file descriptors may be used interchangeably. They refer to the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset is modified by using lseek(2) on one of the file descriptors, the offset is also changed for the other. (see the description of F_SETOWN and F_SETSIG in fcntl(2)).



为了解决这个问题,让每个 child 做自己的/独特的open的两个文件。然后,他们不会共享文件偏移量并且不会有竞争条件。额外的 open/close 的额外开销将是最小的。

还有几点...

信号通常保留用于异常/意外情况(例如段错误等)。因此,使用 abort在 child 中沟通不匹配可以/应该用非零退出代码替换。

这更清洁并提供更大程度的灵 active 。 EXIT_SUCCESS/EXIT_FAILURE是一组相对较新的定义。当做exit(code) ,允许 child 返回任何 7 位错误代码(即 0-127 包括在内)。参见 cmp 的手册页和 rsync举个例子。

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

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

另外,做一个read单个字节非常慢,并且完全抵消了您为并行进程获得的任何速度增益。

因此,即使您在多个进程之间拆分工作,每个子进程仍将不得不循环通过其自己的责任部分的较小 block

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

我还推荐一个跟踪 start/stop 的自定义结构。职位和各种其他每个 child 的数据。

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

注意事项/检查:

不需要比较不同长度的文件。他们永远无法匹配。这里cmp假设文件长度相等。

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

范围/限制/大小计算应加倍检查,因为可能存在“逐一”错误。

另外,请务必使用 off_t (保证在任何 #define 指令之前设置正确的 #include 的 64 位值)以允许程序正确处理大于 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;
}

关于c - 处理多个 fork(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51316303/

相关文章:

c - 无法运行编译后的文件 - bash : ./a.out : Permission denied.(我试过 chmod)

c - 如何使用指针获取字符串并再次打印?

c - 从字符串中删除多余的空格 - C 编程

grails - Grails日志记录在 fork 模式下不起作用

c - fork 重复两次?

c - 在 C 中为 3 维数组分配空间

c - 我的程序中的 scanfs 和/或 ifs 有问题(咖啡店)

c - 从主进程的线程启动子进程是否合适

c++ - 如何让每个子进程获得不同的客户端地址(UDP SOCKET)?

在 C 中通过 fork() 创建子进程