1

我正在与NSSound班级一起玩,以在我自己的后台进程中播放声音,以免阻止用户输入。我决定打电话fork(),但这给我带来了问题。在分配声音的那一刻,分叉的进程崩溃了。有趣的是,如果我构建一个只调用的示例fork(),那么子进程可以NSSound毫无问题地调用,只有当我在调用之前fork()尝试使用其他可可 API 时才会出现崩溃。请参阅带有crashme?()注释的调用的示例:

#import <AppKit/AppKit.h>
#import <math.h>

#define FILENAME \
    "/System/Library/Components/CoreAudio.component/" \
    "Contents/SharedSupport/SystemSounds/dock/drag to trash.aif"

void crashme1(void)
{
    NSString *path = [[NSString alloc] initWithUTF8String:"false file"];
    NSSound *sound = [[NSSound alloc]
        initWithContentsOfFile:path byReference:YES];
}

void crashme2(void)
{
    NSInteger tag = 0;
    [[NSWorkspace sharedWorkspace]
        performFileOperation:NSWorkspaceRecycleOperation
        source:@"." destination:nil
        files:[NSArray arrayWithObject:@"false file"] tag:&tag];
}

double playAif(void)
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString *path = [[NSString alloc] initWithUTF8String:FILENAME];
    NSSound *sound = [[NSSound alloc]
        initWithContentsOfFile:path byReference:YES];
    [path release];

    if (!sound) {
        [pool release];
        return -1;
    }

    const double ret = [sound duration];
    [sound play];
    [sound autorelease];
    [pool release];
    return ret;
}

int main(int argc, char *argv[])
{
    //crashme1();
    //crashme2();
    int childpid = fork();
    if (0 == childpid)  {
        printf("Starting playback\n");
        double wait = playAif();
        sleep(ceil(wait));
        wait = playAif();
        sleep(ceil(wait));
        printf("Finished playback\n");
    }
    return 0;
}

当我从命令行运行它时,它可以工作,但是如果我取消注释对任一crashme?()函数的调用之一,则分叉进程中的播放永远不会开始。是不是因为任何 Cocoa 框架 API 都不是异步信号安全的?有没有办法通过以某种方式包装它们来使调用异步信号安全?

4

1 回答 1

3

我将引用 Leopard CoreFoundation 框架发行说明。我不知道他们是否仍然在线,因为 Apple 倾向于替换而不是扩展 Core Foundation 的发行说明。

CoreFoundation 和 fork()

由于 fork() 的行为,CoreFoundation 不能用于 fork() 的子端。如果您 fork(),则必须在其后调用某种 exec*(),并且不应在 exec*() 之前在子级中使用 CoreFoundation API。这适用于所有使用 CoreFoundation 的高级 API,由于您无法知道这些高级 API 在做什么,以及它们是否使用 CoreFoundation API,因此您也不应该使用任何高级 API。这包括使用 daemon() 函数。

此外,根据 POSIX,只有 async-cancel-safe 函数可以安全地在 fork() 的子端使用,因此即使使用较低级别的 libSystem/BSD/UNIX API 也应保持在最低限度,理想情况下仅使用 async -取消安全功能。

这一直是正确的,过去在各种 Cocoa 开发人员邮件列表中都对此进行了说明。但是 CoreFoundation 现在正在采取一些更强有力的措施来“强制执行”这个限制,所以我们认为添加一个发布说明来指出这一点也是值得的。当某些东西使用在 fork() 之后在 CoreFoundation 中肯定不安全的 API 时,会向 stderr 写入一条消息。但是,如果文件描述符 2 已关闭,您将不会收到任何消息或通知,这太糟糕了。我们试图让进程以一种非常容易识别的方式终止,并且做了一段时间,这非常方便,但是向后二进制兼容性阻止了我们这样做。

换句话说,fork()除了执行一个新程序之外,在 a 的子端做很多事情从来都不是安全的。

除了一般的 POSIX 禁令之外,一个经常提到的解释是:a) 现在几乎所有的 Cocoa 程序都是多线程的,比如 GCD 等等。B) 当你 时fork(),只有调用线程存活到子进程中;其他线程就消失了。由于这些线程可能一直在操纵共享资源,因此子进程不能依赖于具有健全的状态。例如,malloc()实现可能有一个锁来保护共享结构,并且该锁可能在fork(). 因此,如果剩余线程尝试分配内存,它可能会无限期挂起。其他共享数据结构可能只是处于损坏状态。等等。

于 2013-04-13T19:08:09.980 回答