【问题标题】:Create multiple processes using clone()使用 clone() 创建多个进程
【发布时间】:2016-10-23 13:29:14
【问题描述】:

我想使用 clone() 函数(而不是 fork() )创建 3 个进程,并且每个进程都将其 PID 写入标准输出。但它只写入父进程,并且 clone() 由于某种原因不起作用。这是我的代码:

#define _GNU_SOURCE

#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

#define STACK_SIZE 1024

int foo()
{
    printf( "|%d |%d |%d  |%d  |\n", getuid(), getgid(), getpid(), getppid() );
    exit( 1 );
}

int main()
{
    void* stack;
    stack = malloc( STACK_SIZE );
    if( !stack )
    {
        printf( "Stack alloc problem\n" );
        exit( 0 );
    }
    printf( "|UID    |GID    |PID    |PPID   |\n" );
    printf( "|%d |%d |%d  |%d  |Parent\n", getuid(), getgid(), getpid(), getppid() );

    int i = 0;
    for( ; i<3; i++ )
    {
        int err = clone( &foo, ( char * )stack + STACK_SIZE, CLONE_VM, 0 );
        if( err == -1 )
            perror( "clone error" );
    }
    free( stack );
    exit( 1 );
}

【问题讨论】:

  • clone()d 进程在使用CLONE_VM 时共享原始进程的内存空间,因此您为所有克隆提供了相同的堆栈来使用。这不太可能解释你的观察,但它一定会给你带来麻烦。
  • 所以我应该为我创建的每个进程创建单独的堆栈?
  • 是的,如果克隆共享内存,那么每个克隆都必须有一个单独的堆栈。此外,1024 字节的堆栈空间并不多。你需要多少取决于每个克隆要做什么,但是如果你调用任何库函数,那么你基本上无法确定你需要多少。我会尝试至少十倍。
  • 您应该只在所有其他线程都完成后free 堆栈。否则它们可能会崩溃。
  • @immibis 克隆首先执行指定的函数(在本例中为foo()),并在该函数返回时终止。或者他们应该。好歹。 foo() 表面上打印数据

标签: c linux clone


【解决方案1】:

除了 cmets 中已经确定的几个问题:

  • 没有自己的堆栈的孩子
  • 非常小的堆栈
  • 堆栈在孩子确定完成之前被释放
  • foo() 的签名错误

关键问题似乎在于,使用您的 clone() 选项的特定组合,您的子进程被视为线程,而不是独立进程。虽然clone()调用会报告它们各自的PID,但是主线程不能wait()在它们上面,当它退出时,其他线程被杀死。这可能在他们有机会打印输出之前发生。

另外,当线程并发运行printf()时,它们的输出可以混合。

如果你真的想创建线程,最好使用pthread_create(),如果你想创建独立的子进程,最好使用fork()。在任何一种情况下,都有一个明确定义的机制来等待孩子的完成,如果您选择不这样做,还有明确定义的(不同的)语义。

如果您坚持以您的方式使用clone()(特定于Linux),那么您需要滚动您自己的等效于pthread_join()。要获得您想要的输出,您还必须围绕foo()printf() 的调用实现互斥。我选择使用信号量作为第一个目的,这样做后,我使用另一个信号量来实现互斥(而不是引入互斥体)。

您的原始程序的这种变化是有意义的,对我有用:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sched.h>
#include <semaphore.h>

#define STACK_SIZE (1024 * 1024)

sem_t completion_sem;
sem_t mutex_sem;

int foo(void *p)
{
    sem_wait(&mutex_sem);
    printf("|%d |%d |%d  |%d  |\n", getuid(), getgid(), getpid(), getppid());
    sem_post(&mutex_sem);
    sem_post(&completion_sem);
    return 0;
}

#define NUM_CLONES 3

int main()
{
    void* stack;
    pid_t pid;
    int i;

    if (sem_init(&completion_sem, 0, 0) != 0) {
        perror(NULL);
        exit(1);
    }

    if (sem_init(&mutex_sem, 0, 1) != 0) {
        perror(NULL);
        exit(1);
    }

    stack = malloc(STACK_SIZE * NUM_CLONES);
    if (!stack) {
        printf( "Stack alloc problem\n" );
        exit(1);
    }
    printf("|UID    |GID    |PID    |PPID   |\n");
    printf("|%d |%d |%d  |%d  |Parent\n", getuid(), getgid(), getpid(), getppid());

    for (i = 1; i <= NUM_CLONES; i++) {
        int err = clone(&foo, ((char *) stack) + i * STACK_SIZE, CLONE_VM, 0);

        if (err == -1) {
            perror( "clone error" );
            sem_post(&completion_sem);  /* avoids hanging later */
        }
    }

    for (i = 1; i <= NUM_CLONES; i++) {
        sem_wait(&completion_sem);
    }
    sem_destroy(&completion_sem);
    sem_destroy(&mutex_sem);

    free(stack);

    return 0;
}

mutex_sem的用法应该够清楚了。

completion_sem 的使用有点复杂,但并不复杂:它使用值 0 进行初始化。每个子线程在完成之前立即发布(增量)它,并且主线程为每个子线程等待一次。因此,只有在每个子线程发布后,主线程才会清理并退出。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-20
    • 2014-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-19
    • 2013-03-29
    • 1970-01-01
    相关资源
    最近更新 更多