如何从这个递归函数中获取fork()调用的子进程总数?

时间:2022-12-07 22:07:48

I've been messing around with this recursive function trying to get the total number of child processes created by fork(). I can't seem to get it right, though. When I tried to use WEXITSTATUS(), the program output became very erratic. Is there a way to sum the total number of child processes spawned in this function? Is piping the only way to do that, or is there an easier way?

我一直在搞乱这个递归函数,试图获得fork()创建的子进程总数。不过,我似乎无法做到这一点。当我尝试使用WEXITSTATUS()时,程序输出变得非常不稳定。有没有办法将此函数中生成的子进程总数相加?管道是唯一的方法,还是有更简单的方法?

It gets passed ".", to kick the function off in the current working directory. It traverses that directory and all subdirectories, forking whenever a subdirectory is found. verifyFileType() just checks if the file found is a CSV.

它被传递“。”,以在当前工作目录中启动该功能。它遍历该目录和所有子目录,每当找到子目录时都会分叉。 verifyFileType()只检查找到的文件是否为CSV。

**Edited function for some more clarity

**编辑功能更清晰

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>

int traverse(char* directory)
{
    struct dirent *currentDir;  
    DIR *traverser = opendir(directory);

    if (traverser == NULL)
    {
        printf("Error: Could not open directory.\n");
        return 0;
    }

    while ((currentDir = readdir(traverser)) != NULL)
    {       
        if (currentDir->d_type == DT_DIR && strcmp(currentDir->d_name, ".") != 0 && strcmp(currentDir->d_name, "..") != 0)
        {
            PID = fork();

            if (PID == 0)
            {       
                char pathBuffer[1024];
                snprintf(pathBuffer, sizeof(pathBuffer), "%s/%s", directory, currentDir->d_name);

                traverse(pathBuffer);
                //int childProc = traverse(pathBuffer);
                //return childProc + 1;
                exit(0);
            }
            else
            {
                //do parent stuff?
            }
        }
        else if (strcmp(currentDir->d_name, ".") != 0 && strcmp(currentDir->d_name, "..") != 0)
        {
            if (verifyFileType(currentDir->d_name) == 0)
            {
                //if directory = '.', only send file name as fopen() arg
                printf("%s%s\n", directory, currentDir->d_name);
            }
        }
    }

    if (PID > 0)
    {
        int status = 0;
        wait(&status);
        //int returned = WEXITSTATUS(status);
        //return returned + 1;
    }
    else if (PID == -1)
    {
        printf("Error waiting on children.  Aborting.\n");
        _exit(0);
    }

    closedir(traverser);
    return 0;
}

int main (int argc, char **argv)
{
    char* beginningDir = ".";
    rootPID = getpid();

    /*
    int procCount = 0;
    procCount = traverse(beginningDir);
    printf("Proc Count: %d\n", procCount);
    */

    traverse(beginningDir);
    return 0;
}

3 个解决方案

#1


0  

You could try that, this is really close to what you did, but I just count the number of child I spawn and then wait for them.

你可以尝试,这非常接近你所做的,但我只计算我产生的孩子的数量,然后等待它们。

#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>

int traverse(char* directory) {
    struct dirent *currentDir;
    DIR *traverser = opendir(directory);

    if (traverser == NULL) {
        printf("Error: Could not open directory.\n");
        return 0;
    }

    size_t nb_child = 0;
    while ((currentDir = readdir(traverser)) != NULL)
    {
        if (strcmp(currentDir->d_name, ".") == 0
            || strcmp(currentDir->d_name, "..") == 0)
            continue; // ignore . and ..

        // if subdirectory => fork to explore
        if (currentDir->d_type == DT_DIR) {
            pid_t PID = fork();

            if (PID == 0) {
                char pathBuffer[1024];
                snprintf(pathBuffer, sizeof(pathBuffer), "%s/%s",
                         directory, currentDir->d_name);

                return traverse(pathBuffer);
            } else {
                nb_child++; // keep track of the nomber of children
            }
        } else { // non directory "file"
                // Do you verify here
                printf("%s%s\n", directory, currentDir->d_name);
        }
    }

    // loop until we waited for all children
    for (size_t i = 0; i < nb_child; i++)
        wait(NULL);

    closedir(traverser);
    return 0;
}

int main() {
    return traverse(argv[1]);
}

#2


0  

You spawn a number of children in each process corresponding to the number of sub directories but you do not wait for all of them.

您在每个进程中生成了与子目录数相对应的多个子进程,但您不等待所有子目录。

You only wait for the last child. You need to store all the PIDs in an array an wait on them in a loop. Then sum all the WEXITSTATUS from then and return that value (after adding one for your own).

你只等最后一个孩子。您需要将所有PID存储在一个数组中,并在循环中等待它们。然后将所有WEXITSTATUS加起来并返回该值(在为自己添加一个之后)。

#3


0  

I think you can only achieve that using an external entity, as forked processes have nothing in common (except parents).
Let's use a file. We can get a file using tmpfile(). We would need some locking, using flock(fileno(FILE *), ...). Each child would write a single byte into the temp file. After all childs are run, I can get the size of the file - thus I will get the number of childs:

我认为你只能使用外部实体实现这一点,因为分叉进程没有任何共同之处(父母除外)。我们来一个文件。我们可以使用tmpfile()获取文件。我们需要一些锁定,使用flock(fileno(FILE *),...)。每个孩子都会在temp文件中写入一个字节。在运行所有子项之后,我可以获得文件的大小 - 因此我将获得子项的数量:

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>

typedef FILE cnt_t;

/**
 * Create interprocess counter.
 * Should be created once (and only once) by the parent process.
 */
cnt_t *cnt_new(void)
{
    return tmpfile();
}

/**
 * Remove interprocess counter.
 * Should be called by all childs and parent
 */
void cnt_delete(cnt_t *t)
{
    fclose(t);
}

void _cnt_lock(cnt_t *t)
{
    for (int ret; (ret = flock(fileno(t), LOCK_EX)) != 0;) {
        assert(ret == EWOULDBLOCK);
    }
}

void _cnt_unlock(cnt_t *t)
{
    if (flock(fileno(t), LOCK_UN) != 0) {
        assert(0);
    }
}

/**
 * Increments counter by 1.
 */
void cnt_inc(cnt_t *t) {
    assert(t != NULL);
    _cnt_lock(t);

    if (fwrite((char[1]){'X'}, sizeof(char), 1, t) < 0) {
        assert(0);
    }

    if (fflush(t) != 0) {
        assert(0);
    }

    _cnt_unlock(t);
}

void cnt_println(cnt_t *t)
{
    _cnt_lock(t);

    if (fseek(t, 0L, SEEK_SET) < 0) {
        assert(0);
    }

    char buf[124];
    size_t cnt = fread(buf, sizeof(char), 124, t);
    printf("cnt(%p) = %ld  '%.*s'\n", cnt, (void*)t, cnt, buf);

    _cnt_unlock(t);
}

/**
 * Get's counter value.
 */
long cnt_getCount(cnt_t *t)
{
    assert(t != NULL);

    _cnt_lock(t);

    if (fseek(t, 0L, SEEK_END) < 0) {
        assert(0);
    }

    const long sz = ftell(t);
    if (sz < 0) {
        assert(0);
    }

    _cnt_unlock(t);

    return sz;
}

/* ----------------------------------------------------------- */

int main()
{
    srand(0);

    cnt_t *cntobj = cnt_new();

    bool child = false;
    for (int i = 0; i < 5; ++i) {
        const int ret = fork();
        switch (ret) {
        case 0:
            cnt_inc(cntobj);
            child = true;
            break;
        case -1:
            fprintf(stderr, "fork error!\n");
            exit(-1);
            break;
        default:
            fprintf(stderr, "%d -> %d\n", getpid(), ret);
            break;
        }
    }

    while (wait(NULL) != -1) continue;

    if (child) {
        cnt_delete(cntobj);
        exit(0);
    }

    const long cnt = cnt_getCount(cntobj);
    cnt_delete(cntobj);

    fprintf(stderr, "childs %ld\n", cnt);

    return 0;
}

Maybe I went too much on obfuscucation (typedef FILE cnt_t looks strange), but the code works and returns the correct number 31. Live version available at jdoodle.

也许我过多地讨论了obfuscucation(typedef FILE cnt_t看起来很奇怪),但代码工作并返回正确的数字31.在jdoodle上可用的实时版本。

And here's a solution just by using pipes:

这是一个只使用管道的解决方案:

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>

typedef struct {
    int p[2];
} cnt_t;

/**
 * Create interprocess counter.
 * Should be created once (and only once) by the parent process.
 */
cnt_t *cnt_new(void)
{
    cnt_t *t = malloc(sizeof(*t));
    assert(t != NULL);
    if (pipe(t->p) < 0) {
        assert(0);
    }
    if (fcntl(t->p[0], F_SETFL, O_NONBLOCK) < 0) {
        assert(0);
    }
    return t;
}

/**
 * Remove interprocess counter.
 * Should be called by all childs and parent
 */
void cnt_delete(cnt_t *t)
{
    close(t->p[0]);
    close(t->p[1]);
    t->p[0] = 0;
    t->p[1] = 0;
    free(t);
}

/**
 * Increments counter by 1.
 */
void cnt_inc(cnt_t *t) 
{
    assert(t != NULL);
    if (write(t->p[1], (char[1]){'X'}, 1 * sizeof(char)) < 0) {
        assert(0);
    }
}

/**
 * Get's counter value.
 */
long cnt_getCount(cnt_t *t)
{
    assert(t != NULL);

    char c;
    long cnt = 0;
    ssize_t tmp;
    errno = 0;
    while ((tmp = read(t->p[0], &c, 1)) == 1) {
        ++cnt;
    }
    if (tmp < 0 && errno != EWOULDBLOCK) {
        assert(0);
    }

    const long ret = cnt;

    while (cnt--) {
        if (write(t->p[1], (char[1]){'X'}, 1) < 0) {
            assert(0);
        }
    }

    return ret;
}

/* ----------------------------------------------------------- */

int main()
{
    srand(0);

    cnt_t *cntobj = cnt_new();

    bool child = false;
    for (int i = 0; i < 5; ++i) {
        const int ret = fork();
        switch (ret) {
        case 0:
            cnt_inc(cntobj);
            child = true;
            break;
        case -1:
            fprintf(stderr, "fork error!\n");
            exit(-1);
            break;
        default:
            fprintf(stderr, "%d -> %d\n", getpid(), ret);
            break;
        }
    }

    while (wait(NULL) != -1) continue;

    if (child) {
        cnt_delete(cntobj);
        exit(0);
    }

    const long cnt = cnt_getCount(cntobj);
    cnt_delete(cntobj);

    fprintf(stderr, "childs %ld\n", cnt);

    return 0;
}

Works as good and probably is way faster. Live version still at jdoodle.

工作得很好,可能更快。现场版仍然在jdoodle。

These examples use a stupid error handling using assert and they serve just only to show the method and that the method works.

这些示例使用使用assert的愚蠢错误处理,它们仅用于显示方法并且该方法有效。

Probably we could also create a solution using posix shemaphores and some interprocess communication, some shmget and semop and for example queuing sempahore count.

也许我们也可以使用posix信号量和一些进程间通信创建一个解决方案,一些shmget和semop,例如排队sempahore计数。

#1


0  

You could try that, this is really close to what you did, but I just count the number of child I spawn and then wait for them.

你可以尝试,这非常接近你所做的,但我只计算我产生的孩子的数量,然后等待它们。

#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>

int traverse(char* directory) {
    struct dirent *currentDir;
    DIR *traverser = opendir(directory);

    if (traverser == NULL) {
        printf("Error: Could not open directory.\n");
        return 0;
    }

    size_t nb_child = 0;
    while ((currentDir = readdir(traverser)) != NULL)
    {
        if (strcmp(currentDir->d_name, ".") == 0
            || strcmp(currentDir->d_name, "..") == 0)
            continue; // ignore . and ..

        // if subdirectory => fork to explore
        if (currentDir->d_type == DT_DIR) {
            pid_t PID = fork();

            if (PID == 0) {
                char pathBuffer[1024];
                snprintf(pathBuffer, sizeof(pathBuffer), "%s/%s",
                         directory, currentDir->d_name);

                return traverse(pathBuffer);
            } else {
                nb_child++; // keep track of the nomber of children
            }
        } else { // non directory "file"
                // Do you verify here
                printf("%s%s\n", directory, currentDir->d_name);
        }
    }

    // loop until we waited for all children
    for (size_t i = 0; i < nb_child; i++)
        wait(NULL);

    closedir(traverser);
    return 0;
}

int main() {
    return traverse(argv[1]);
}

#2


0  

You spawn a number of children in each process corresponding to the number of sub directories but you do not wait for all of them.

您在每个进程中生成了与子目录数相对应的多个子进程,但您不等待所有子目录。

You only wait for the last child. You need to store all the PIDs in an array an wait on them in a loop. Then sum all the WEXITSTATUS from then and return that value (after adding one for your own).

你只等最后一个孩子。您需要将所有PID存储在一个数组中,并在循环中等待它们。然后将所有WEXITSTATUS加起来并返回该值(在为自己添加一个之后)。

#3


0  

I think you can only achieve that using an external entity, as forked processes have nothing in common (except parents).
Let's use a file. We can get a file using tmpfile(). We would need some locking, using flock(fileno(FILE *), ...). Each child would write a single byte into the temp file. After all childs are run, I can get the size of the file - thus I will get the number of childs:

我认为你只能使用外部实体实现这一点,因为分叉进程没有任何共同之处(父母除外)。我们来一个文件。我们可以使用tmpfile()获取文件。我们需要一些锁定,使用flock(fileno(FILE *),...)。每个孩子都会在temp文件中写入一个字节。在运行所有子项之后,我可以获得文件的大小 - 因此我将获得子项的数量:

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>

typedef FILE cnt_t;

/**
 * Create interprocess counter.
 * Should be created once (and only once) by the parent process.
 */
cnt_t *cnt_new(void)
{
    return tmpfile();
}

/**
 * Remove interprocess counter.
 * Should be called by all childs and parent
 */
void cnt_delete(cnt_t *t)
{
    fclose(t);
}

void _cnt_lock(cnt_t *t)
{
    for (int ret; (ret = flock(fileno(t), LOCK_EX)) != 0;) {
        assert(ret == EWOULDBLOCK);
    }
}

void _cnt_unlock(cnt_t *t)
{
    if (flock(fileno(t), LOCK_UN) != 0) {
        assert(0);
    }
}

/**
 * Increments counter by 1.
 */
void cnt_inc(cnt_t *t) {
    assert(t != NULL);
    _cnt_lock(t);

    if (fwrite((char[1]){'X'}, sizeof(char), 1, t) < 0) {
        assert(0);
    }

    if (fflush(t) != 0) {
        assert(0);
    }

    _cnt_unlock(t);
}

void cnt_println(cnt_t *t)
{
    _cnt_lock(t);

    if (fseek(t, 0L, SEEK_SET) < 0) {
        assert(0);
    }

    char buf[124];
    size_t cnt = fread(buf, sizeof(char), 124, t);
    printf("cnt(%p) = %ld  '%.*s'\n", cnt, (void*)t, cnt, buf);

    _cnt_unlock(t);
}

/**
 * Get's counter value.
 */
long cnt_getCount(cnt_t *t)
{
    assert(t != NULL);

    _cnt_lock(t);

    if (fseek(t, 0L, SEEK_END) < 0) {
        assert(0);
    }

    const long sz = ftell(t);
    if (sz < 0) {
        assert(0);
    }

    _cnt_unlock(t);

    return sz;
}

/* ----------------------------------------------------------- */

int main()
{
    srand(0);

    cnt_t *cntobj = cnt_new();

    bool child = false;
    for (int i = 0; i < 5; ++i) {
        const int ret = fork();
        switch (ret) {
        case 0:
            cnt_inc(cntobj);
            child = true;
            break;
        case -1:
            fprintf(stderr, "fork error!\n");
            exit(-1);
            break;
        default:
            fprintf(stderr, "%d -> %d\n", getpid(), ret);
            break;
        }
    }

    while (wait(NULL) != -1) continue;

    if (child) {
        cnt_delete(cntobj);
        exit(0);
    }

    const long cnt = cnt_getCount(cntobj);
    cnt_delete(cntobj);

    fprintf(stderr, "childs %ld\n", cnt);

    return 0;
}

Maybe I went too much on obfuscucation (typedef FILE cnt_t looks strange), but the code works and returns the correct number 31. Live version available at jdoodle.

也许我过多地讨论了obfuscucation(typedef FILE cnt_t看起来很奇怪),但代码工作并返回正确的数字31.在jdoodle上可用的实时版本。

And here's a solution just by using pipes:

这是一个只使用管道的解决方案:

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>

typedef struct {
    int p[2];
} cnt_t;

/**
 * Create interprocess counter.
 * Should be created once (and only once) by the parent process.
 */
cnt_t *cnt_new(void)
{
    cnt_t *t = malloc(sizeof(*t));
    assert(t != NULL);
    if (pipe(t->p) < 0) {
        assert(0);
    }
    if (fcntl(t->p[0], F_SETFL, O_NONBLOCK) < 0) {
        assert(0);
    }
    return t;
}

/**
 * Remove interprocess counter.
 * Should be called by all childs and parent
 */
void cnt_delete(cnt_t *t)
{
    close(t->p[0]);
    close(t->p[1]);
    t->p[0] = 0;
    t->p[1] = 0;
    free(t);
}

/**
 * Increments counter by 1.
 */
void cnt_inc(cnt_t *t) 
{
    assert(t != NULL);
    if (write(t->p[1], (char[1]){'X'}, 1 * sizeof(char)) < 0) {
        assert(0);
    }
}

/**
 * Get's counter value.
 */
long cnt_getCount(cnt_t *t)
{
    assert(t != NULL);

    char c;
    long cnt = 0;
    ssize_t tmp;
    errno = 0;
    while ((tmp = read(t->p[0], &c, 1)) == 1) {
        ++cnt;
    }
    if (tmp < 0 && errno != EWOULDBLOCK) {
        assert(0);
    }

    const long ret = cnt;

    while (cnt--) {
        if (write(t->p[1], (char[1]){'X'}, 1) < 0) {
            assert(0);
        }
    }

    return ret;
}

/* ----------------------------------------------------------- */

int main()
{
    srand(0);

    cnt_t *cntobj = cnt_new();

    bool child = false;
    for (int i = 0; i < 5; ++i) {
        const int ret = fork();
        switch (ret) {
        case 0:
            cnt_inc(cntobj);
            child = true;
            break;
        case -1:
            fprintf(stderr, "fork error!\n");
            exit(-1);
            break;
        default:
            fprintf(stderr, "%d -> %d\n", getpid(), ret);
            break;
        }
    }

    while (wait(NULL) != -1) continue;

    if (child) {
        cnt_delete(cntobj);
        exit(0);
    }

    const long cnt = cnt_getCount(cntobj);
    cnt_delete(cntobj);

    fprintf(stderr, "childs %ld\n", cnt);

    return 0;
}

Works as good and probably is way faster. Live version still at jdoodle.

工作得很好,可能更快。现场版仍然在jdoodle。

These examples use a stupid error handling using assert and they serve just only to show the method and that the method works.

这些示例使用使用assert的愚蠢错误处理,它们仅用于显示方法并且该方法有效。

Probably we could also create a solution using posix shemaphores and some interprocess communication, some shmget and semop and for example queuing sempahore count.

也许我们也可以使用posix信号量和一些进程间通信创建一个解决方案,一些shmget和semop,例如排队sempahore计数。