UNIX 的怪东西
创始人
2024-03-01 23:52:23
0

最近我在用我编写的各种工具做更多 UNIX 下的事情,我遇到了两个有趣的问题。这些都不是 “bug”,而是我没想到的行为。

线程安全的 printf

我有一个 C 程序从磁盘读取一些图像,进行一些处理,并将有关这些图像的输出写入 STDOUT。伪代码:

for(imagefilename in images)
{
    results = process(imagefilename);
    printf(results);
}

对于每个图像都是独立处理的,因此我自然希望将处理任务分配在各个 CPU 之间以加快速度。我通常使用 fork(),所以我写了这个:

for(child in children)
{
    pipe = create_pipe();
    worker(pipe);
}

// main parent process
for(imagefilename in images)
{
    write(pipe[i_image % N_children], imagefilename)
}

worker()
{
    while(1)
    {
        imagefilename = read(pipe);
        results = process(imagefilename);
        printf(results);
    }
}

这是正常的做法:我为 IPC 创建管道,并通过这些管道给子进程 worker 发送图像名。每个 worker 能够通过另一组管道将其结果写回主进程,但这很痛苦,所以每个 worker 都直接写入共享 STDOUT。这工作正常,但正如人们所预料的那样,对 STDOUT 的写入发生冲突,因此各种图像的结果最终会混杂在一起。那很糟糕。我不想自己设置个锁,但幸运的是 GNU libc 为它提供了函数:flockfile()。我把它们放进去了……但是没有用!为什么?因为 flockfile() 最终因为 fork() 的写时复制行为而被限制在单个子进程中。即 fork()提供的额外安全性(与线程相比),这实际上最终破坏了锁。

我没有尝试使用其他锁机制(例如 pthread 互斥锁),但我可以想象它们会遇到类似的问题。我想保持简单,所以将输出发送回父输出是不可能的:这给程序员和运行程序的计算机制造了更多的工作。

解决方案:使用线程而不是 fork()。这有制造冗余管道的好的副作用。最终的伪代码:

for(children)
{
    pthread_create(worker, child_index);
}
for(children)
{
    pthread_join(child);
}

worker(child_index)
{
    for(i_image = child_index; i_image < N_images; i_image += N_children)
    {
        results = process(images[i_image]);
        flockfile(stdout);
        printf(results);
        funlockfile(stdout);
    }
}

这更简单,如预期的那样工作。我猜有时线程更好。

将部分读取的文件传递给子进程

对于各种 vnlog 工具,我需要实现这个操作序列:

  1. 进程打开一个关闭 O_CLOEXEC 标志的文件
  2. 进程读取此文件的一部分(在 vnlog 的情况下直到图例的末尾)
  3. 进程调用 exec() 以调用另一个程序来处理已经打开的文件的其余部分

第二个程序可能需要命令行中的文件名而不是已打开的文件描述符,因为第二个程序可能自己调用 ​​open()。如果我传递文件名,这个新程序将重新打开文件,然后从头开始读取文件,而不是从原始程序停止的位置开始读取。在我的程序上不可以这样做,因此将文件名传递给第二个程序是行不通的。

所以我真的需要以某种方式传递已经打开的文件描述符。我在使用 Linux(其他操作系统可能在这里表现不同),所以我理论上可以通过传递 /dev/fd/N 而不是文件名来实现。但事实证明这也不起作用。在 Linux上(再说一次,也许是特定于 Linux)对于普通文件 /dev/fd/N 是原始文件的符号链接。所以这最终做的是与传递文件名完全相同的事情。

但有一个临时方案!如果我们正在读取管道而不是文件,那么没有什么可以符号链接,并且 /dev/fd/N 最终将原始管道传递给第二个进程,然后程序正常工作。我可以通过将上面的 open("filename") 更改为 popen("cat filename") 之类的东西来伪装。呸!这真的是我们所能做到最好的吗?这在 BSD 上看上去会怎么样?


via: http://notes.secretsauce.net/notes/2018/08/03_unix-curiosities.html

作者:Dima Kogan 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

相关内容

【UNIX 环境编程】GC...
💭 写在前面:本文将介绍如何使用 G...
2025-05-31 02:18:08
在 Linux/Unix ...
在 Linux/Unix 系统中,动态库(.so 文件)的搜索路径...
2025-05-21 10:17:25
深入解析:Linux、Wi...
在现代互联网世界中,服务器操作系统扮演着至关重要的角色。无论是大型...
2025-03-18 06:12:31
选择合适的服务器操作系统:...
在如今这个信息化、数字化的时代,服务器扮演着至关重要的角色。它们是...
2025-02-25 10:46:43
UNIX_T·杨宇 :HE...
UNIX_T时间截(英文为Unix epoch,Unix time...
2025-02-18 16:18:55

热门资讯

Helix:高级 Linux ... 说到 基于终端的文本编辑器,通常 Vim、Emacs 和 Nano 受到了关注。这并不意味着没有其他...
使用 KRAWL 扫描 Kub... 用 KRAWL 脚本来识别 Kubernetes Pod 和容器中的错误。当你使用 Kubernet...
JStock:Linux 上不... 如果你在股票市场做投资,那么你可能非常清楚投资组合管理计划有多重要。管理投资组合的目标是依据你能承受...
通过 SaltStack 管理... 我在搜索Puppet的替代品时,偶然间碰到了Salt。我喜欢puppet,但是我又爱上Salt了:)...
Epic 游戏商店现在可在 S... 现在可以在 Steam Deck 上运行 Epic 游戏商店了,几乎无懈可击! 但是,它是非官方的。...
《Apex 英雄》正式可在 S... 《Apex 英雄》现已通过 Steam Deck 验证,这使其成为支持 Linux 的顶级多人游戏之...
如何在 Github 上创建一... 学习如何复刻一个仓库,进行更改,并要求维护人员审查并合并它。你知道如何使用 git 了,你有一个 G...
2024 开年,LLUG 和你... Hi,Linuxer,2024 新年伊始,不知道你是否已经准备好迎接新的一年~ 2024 年,Lin...
什么是 KDE Connect... 什么是 KDE Connect?它的主要特性是什么?它应该如何安装?本文提供了基本的使用指南。科技日...
从 Yum 更新中排除特定/某... 作为系统更新的一部分,你也许需要在基于 Red Hat 系统中由于应用依赖排除一些软件包。如果是,如...