Linux FHS

我们常用Linux发行版本,各种类型文件通常放到不同的目录里,比如二进制文件放置到/bin、/sbin、/usr/bin、/usr/sbin,系统和应用配置文件放置到/etc,/home是非root用户的主目录(/home/{username}),/root是root用户的主目录……

那么为什么要这样放置文件?

这个其实就是Linux基金会制定的FHS标准。

文件系统层次结构标准(Filesystem Hierarchy Standard,FHS)定义了Linux操作系统中的主要目录及目录内容。FHS由Linux基金会维护。 当前版本为3.0版,于2015-06-03发布。具体的FHS规范内容可以看FHS官网。

注意:

  1. 虽然主流的发行版本基本实现了FHS标准,但是在实现细节上与标准还是有一些不一致的地方。还有一些小众的发行版本压根就没有遵循FHS标准。
  2. 这些年新起的Snap、Flatpak、AppImage等应用程序包管理方式,不再像以前的apt、rpm、dnf等遵循FHS规范,而是将应用程序的二进制文件、配置文件、依赖文件等都打包到一个目录里。

 

References:

  1. FHS Wiki https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard
  2. FHS 3.0 https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html

 

查看Linux内核和发行版本的方法

查看Linux发行版本的方法

  • cat /etc/issue,适用于所有的发行版本
  • lsb_release -a,也适用于所有的发行版本,但是有些发行版本(尤其是一些精简版的镜像)可能没有预置lsb_release,需要手动安装。
  • cat /etc/redhat-release,这种方法只适合Redhat系的发行版本,包括CentOS
  • cat /etc/centos-release,只适用于CentOS

 

查看Linux内核版本命令

  • cat /proc/version
  • uname -a

Unix Timestamp(Unix时间戳)

UNIX时间,或称POSIX时间是UNIX或类UNIX系统使用的时间表示方式:从UTC 1970年1月1日0时0分0秒起至现在的总秒数,不考虑闰秒。在多数Unix系统上Unix时间可以透过date +%s指令来检查。(Unix time (also known as Epoch timePOSIX timeseconds since the Epoch, or UNIX Epoch time) is a system for describing a point in time. It is the number of seconds that have elapsed since the Unix epoch.) (英 [ˈiːpɒk]   美 [ˈepək])

从这个解释可以看出来,同一时刻,在全世界任一时区,获取的Unix时间戳是相同的。

所以,针对PHP而言,time()函数获取的到时间戳与时区无关。

time ( ) : int

Returns the current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT).

那么,进一步延伸,对于一个给定的日期时间字符串,例如:2020-01-01 00:00:00,那么获取这个日期时间对应的时间戳就是与时区有关的。因为不同时区下的2020-01-01 00:00:00距离UTC的1970-01-01 00:00:00的时间间隔是不一样的。

strtotime ( string $datetime [, int $now = time() ] ) : int

对于PHP而言,在使用strtotime函数时,如果日期时间字符串中没有包含时区信息,那么会使用默认的时区date_default_timezone_get()。(Each parameter of this function uses the default time zone unless a time zone is specified in that parameter. Be careful not to use different time zones in each parameter unless that is intended. )

$dateStr = '2020-01-01 00:00:00';

$timezone = 'Asia/Shanghai';
date_default_timezone_set($timezone);
echo sprintf("%13s)%25s ==> %s\n", $timezone, $dateStr, strtotime($dateStr));

$dateStrWithTimezone = '2020-01-01T00:00:00+08:00';
date_default_timezone_set($timezone);//优先读取日期时间字符串里的时区信息,此处单独设置的时区对下一行的strtotime无效
echo sprintf("%13s)%25s ==> %s\n", $timezone, $dateStrWithTimezone, strtotime($dateStrWithTimezone));

$timezone = 'UTC';
date_default_timezone_set($timezone);
echo sprintf("%13s)%25s ==> %s\n", $timezone, $dateStr, strtotime($dateStr));
echo sprintf("%13s)%25s ==> %s\n", $timezone, $dateStrWithTimezone, strtotime($dateStrWithTimezone));

output:
Asia/Shanghai)2020-01-01 00:00:00 ==> 1577808000
Asia/Shanghai)2020-01-01T00:00:00+08:00 ==> 1577808000
UTC)2020-01-01 00:00:00 ==> 1577836800
UTC)2020-01-01T00:00:00+08:00 ==> 1577808000 //优先读取日期时间字符串里的时区信息,runtime设置的时区对strtotime无效

 

date ( string $format [, int $timestamp = time() ] ) : string

Format a local time/date

同样的道理,同一个时间戳在不同的时区下,对应的日期时间字符串是不一样的。

date_default_timezone_set('UTC');
$timestamp = 1577836800;//UTC 2020-01-01 00:00:00
$sourceDatetime = new \DateTime(date('Y-m-d H:i:s', $timestamp));
echo sprintf("source datetime:%s(%s)\n", $sourceDatetime->format('Y-m-d H:i:s'), $sourceDatetime->getTimezone()->getName());

$timezone = 'Asia/Shanghai';
$targetDatetime = (new \DateTime(date('Y-m-d H:i:s', $timestamp)))
    ->setTimezone(new \DateTimeZone($timezone));
echo sprintf("target datetime:%s(%s)\n", $targetDatetime->format('Y-m-d H:i:s'), $targetDatetime->getTimezone()->getName());

output:
source datetime:2020-01-01 00:00:00(UTC)
target datetime:2020-01-01 08:00:00(Asia/Shanghai)

 

References:

https://zh.wikipedia.org/wiki/UNIX时间

记录cp命令在GNU/Linux和BSD(MacOS)下表现不一致的一个行为

最后更新时间:2021-04-22

重现步骤如下:

现有的目录结构:

├── source

│   └── source.txt

├── target

 

cp -r source target1   //target1 dir not exists

GUN/BSD
├── source

│   └── source.txt

├── target

└── target1

└── source.txt

 

cp -r source target   //target dir exists

GUN/Linux BSD(MacOS)
├── source

│   └── source.txt

├── target

└── source

└── source.txt

├── source

│   └── source.txt

├── target

│   └── source.txt

测试环境:

GNU coreutils 8.22(CentOS)

GNU coreutils 8.30(Ubuntu 20.04)

BSD macOS Catalina 10.15.7

 

总结/Summary:

使用cp -r source target 复制目录时,如果目标目录不存在,则GUN/Linux&BSD(mac OS)的cp行为一致;如果目录已经存在,则BSD(mac OS)下会和目标目录不存在时行为一致,但是GUN的cp会创建在目标目录下创建子目录。

 

PS:

如果目标目录已经存在,可以使用 cp -r source/.  target (GUN/Linux下target目录存在or不存在都支持,BSD(mac OS)下target目录存在or不存在都支持) or   cp -r source/* target(仅适用于GUN/Linux下target目录存在的情况,BSD(mac OS)下语法错误)

所以简单起见:直接使用cp -r source/.  target。GUN/Linux和BSD(mac OS)都支持,并且target目录存不存在都支持。

 

Console and TTY and PTY

Console:

在早期的电脑上,往往具有带有大量开关和指示灯的面板,可以对电脑进行一些底层的操作,这个面板就叫做Console。其概念来自于管风琴的控制台。一台电脑通常只能有一个Console,很多时候是电脑主机的一部分,和CPU共享一个机柜。

 

Terminal and TTY(终端):

一台大型主机往往需要支持许多用户同时使用,每个用户所使用操作的设备,就叫做Terminal——终端,终端使用通信电缆与电脑主机连接,甚至可以通过电信网络(电话、电报线路等等)连接另一个城市的电脑。

TTY是电传打字机Teletypewriter的缩写,TTY曾经是最流行的终端设备。现在大多数场景TTY基本就是表示terminal。

Linux有7个TTY?
从控制台切换到 X 窗口,一般采用Ctrl+Alt+F7 ,为什么呢?因为系统默认定义了6个虚拟控制台(Ctrl+Alt+F1 ~ F6), 7个用于x-windows实际上,很多人一般不会需要这么多虚拟控制台的,关闭多余的控制台减少对系统资源的占用,可以自己更改配置文件减少它的数量(在支持systemd的系统里,配置文件位置:/etc/systemd/logind.conf, https://freedesktop.org/software/systemd/man/logind.conf.html,所以具体系统开启了多少个tty,要看配置文件,可能各个发行版本不一样,比如Ubuntu18.04/20.04就是默认6个,而且默认是把tty1留给了x-windows(Ctrl+Alt+F1))。

 

PTY(Pseudo TerminalPseudo TTY, or PTY, 虚拟终端/伪终端):

pts/ptmx(pts/ptmx结合使用,进而实现pty)

所谓伪终端是逻辑上的终端设备,多用于模拟终端程序。例如,我们在X Window下打开的终端,以及我们在Windows使用telnet 或ssh等方式登录Linux主机,此时均在使用pty设备(准确的说在使用pty从设备)

 

echo test > /dev/tty1会把单词test发送到连接在tty1的设备上。

1、/dev/pts0, /dev/pts1, …

2、/dev/tty0, …

3、/dev/console 表示物理终端(控制台)

4、/dev/ttyS0, …  表示串行终端

tty命令可以查看当前登录的终端类型,比如/dev/tty2,/dev/pts/0

grep不显示自己

最后更新时间:2022-05-31

要想在使用grep时不显示自己,常规有下面两种做法

ps -ef | grep "nginx"|grep -v "grep"

ps -ef | grep "[n]ginx"

grep -v 过滤grep自身可以通过man获取帮助。但是grep “[n]ginx”是如何实现不显示grep本身的了?

寻思很久,终于在stackoverflow上看到了一个醍醐灌顶的答案。

ps -ef | grep “[n]ginx” 命令中,grep 命令的参数是“[n]ginx”,正则[n]ginx当然不能匹配它!
ps -ef | grep "[n]ginx" 命令中,grep 命令的参数是正则表达式"[n]ginx",正则表达式"[n]ginx"可以匹配文本"nginx",但是不能匹配"[n]ginx"啊。

PS:我们使用grep时,也可能这样写ps -ef | grep [n]ginx (即grep的参数不带引号),虽然也是能工作,但是man文档建议大家还是要用引号包裹参数。

man grep:

grep searches for PATTERNS in each FILE. PATTERNS is one or more patterns separated by newline characters, and grep prints each line that matches a pattern. Typically PATTERNS should be
quoted when grep is used in a shell command.

C/C++网络编程中的TCP保活

在默认的情况下,TCP连接是没有保活的心跳的。这就是说,当一个TCP的socket,客户端与服务端谁也不发送数据,会一直保持着连接。这其中如果有一方异常掉线,另一端永远也不可能知道。这对于一些服务型的程序来说,将是灾难性的后果。

  所以,必须对创建的socket,启用保活心跳,即Keepalive选项。

启用Keepalive

  对于WIN32或者Linux平台来说,设置socket的Keepalive都很简单,只需使用setsockopt设置SO_KEEPALIVE即可。

  setsockopt的函数原型在Linux环境下为:

[cpp]
  1. #include <sys/types.h>   
  2. #include <sys/socket.h>   
  3.   
  4. int setsockopt(int s, int level, int optname,  
  5.                const void *optval,  
  6.                socklen_t optlen);  

,在WIN32平台下为

[cpp]
  1. #include <winsock2.h>   
  2.   
  3. int setsockopt(int s, int level, int optname,  
  4.                const char *optval,  
  5.                int optlen);  

  因为const void *可以接受const char *型的参数,所以为了代码的跨平台编译考虑,可以采用以下代码来设置TCP的Keepalive选项。

[cpp]
  1. alive = 1;  
  2. if (setsockopt  
  3.     (fd, SOL_SOCKET, SO_KEEPALIVE, (const char *) &alive,  
  4.      sizeof alive) != 0)  
  5.   {  
  6.     log_warn (“Set keep alive error: %s.\n”, strerror (errno));  
  7.     return -1;  
  8.   }  

  这样,对于TCP的连接,就启用了系统默认值的保活心跳。

Linux环境下的TCP Keepalive参数设置

  为什么说是系统默认值的呢?因为有这样几个值,我们并没有手动设置,是采用的系统默认值。即,

  1. 多长时间发送一次保活心跳?
  2. 如果没有返回,多长时间再重试发送?
  3. 重试几次为失败?  如果是Linux操作系统,这三个值分别为
[plain]
  1. # cat /proc/sys/net/ipv4/tcp_keepalive_time  
  2. 7200  
  3. # cat /proc/sys/net/ipv4/tcp_keepalive_intvl  
  4. 75  
  5. # cat /proc/sys/net/ipv4/tcp_keepalive_probes  
  6. 9  

  这就是说,在Linux系统下,如果对于TCP的socket启用了Keepalive选项,则会在7200秒(即两个小时)没有数据后,发起KEEPALIVE报文。如果没有回应,则会在75秒后再次重试。如果重试9次均失败,则认定连接已经失效。TCP的读取操作,将返回0。

  这对于我们大多数应用来说,前两个时间值都有点太长了。

  我们可以通过重设上面三个值,来使得操作系统上运行的所有启用了Keepalive选项的TCP的socket的行为更改。

  我们也可以只针对我们自己创建的socket,重设这三个值。它们分别对应TCP_KEEPIDLE、TCP_KEEPINTL和TCP_KEEPCNT的选项值,同样可以使用setsockopt进行设置。

[cpp]
  1. #include <stdlib.h>   
  2. #include <fcntl.h>   
  3. #include <errno.h>   
  4. #include <sys/socket.h>   
  5. #include <netinet/tcp.h>   
  6. #include <netinet/in.h>   
  7. #include <netdb.h>   
  8. #include <arpa/inet.h>   
  9.   
  10. int  
  11. socket_set_keepalive (int fd)  
  12. {  
  13.   int ret, error, flag, alive, idle, cnt, intv;  
  14.   
  15.   /* Set: use keepalive on fd */  
  16.   alive = 1;  
  17.   if (setsockopt  
  18.       (fd, SOL_SOCKET, SO_KEEPALIVE, &alive,  
  19.        sizeof alive) != 0)  
  20.     {  
  21.       log_warn (“Set keepalive error: %s.\n”, strerror (errno));  
  22.       return -1;  
  23.     }  
  24.   
  25.   /* 10秒钟无数据,触发保活机制,发送保活包 */  
  26.   idle = 10;  
  27.   if (setsockopt (fd, SOL_TCP, TCP_KEEPIDLE, &idle, sizeof idle) != 0)  
  28.     {  
  29.       log_warn (“Set keepalive idle error: %s.\n”, strerror (errno));  
  30.       return -1;  
  31.     }  
  32.   
  33.   /* 如果没有收到回应,则5秒钟后重发保活包 */  
  34.   intv = 5;  
  35.   if (setsockopt (fd, SOL_TCP, TCP_KEEPINTVL, &intv, sizeof intv) != 0)  
  36.     {  
  37.       log_warn (“Set keepalive intv error: %s.\n”, strerror (errno));  
  38.       return -1;  
  39.     }  
  40.   
  41.   /* 连续3次没收到保活包,视为连接失效 */  
  42.   cnt = 3;  
  43.   if (setsockopt (fd, SOL_TCP, TCP_KEEPCNT, &cnt, sizeof cnt) != 0)  
  44.     {  
  45.       log_warn (“Set keepalive cnt error: %s.\n”, strerror (errno));  
  46.       return -1;  
  47.     }  
  48.   
  49.   return 0;  
  50. }  

WIN32环境下的TCP Keepalive参数设置

  而WIN32环境下的参数设置,就要麻烦一些,需要使用另外的一个函数WSAIoctl和一个结构struct tcp_keepalive。

  它们的原型分别为:

[cpp]
  1. #include <winsock2.h>   
  2. #include <mstcpip.h>   
  3.   
  4. int WSAIoctl(  
  5.              SOCKET s,  
  6.              DWORD dwIoControlCode,  
  7.              LPVOID lpvInBuffer,  
  8.              DWORD cbInBuffer,  
  9.              LPVOID lpvOutBuffer,  
  10.              DWORD cbOutBuffer,  
  11.              LPDWORD lpcbBytesReturned,  
  12.              LPWSAOVERLAPPED lpOverlapped,  
  13.              LPWSAOVERLAPPED_COMPLETION lpCompletionRoutine  
  14. );  
  15.   
  16. struct tcp_keepalive {  
  17.     u_long onoff;  
  18.     u_long keepalivetime;  
  19.     u_long keepaliveinterval;  
  20. };  

  在这里,使用WSAIoctl的时候,dwIoControlCode要使用SIO_KEEPALIVE_VALS,lpvOutBuffer用不上,cbOutBuffer必须设置为0。

  struct tcp_keepalive结构的参数意义为:

  onoff,是否开启KEEPALIVE; keepalivetime,多长时间触发Keepalive报文的发送; keepaliveinterval,多长时间没有回应触发下一次发送。

  注意:这里两个时间单位都是毫秒而不是秒。

[cpp]
  1. #include <winsock2.h>   
  2. #include <mstcpip.h>   
  3.   
  4. int  
  5. socket_set_keepalive (int fd)  
  6. {  
  7.   struct tcp_keepalive kavars[1] = {  
  8.       1,  
  9.       10 * 1000,        /* 10 seconds */  
  10.       5 * 1000          /* 5 seconds */  
  11.   };  
  12.   
  13.   /* Set: use keepalive on fd */  
  14.   alive = 1;  
  15.   if (setsockopt  
  16.       (fd, SOL_SOCKET, SO_KEEPALIVE, (const char *) &alive,  
  17.        sizeof alive) != 0)  
  18.     {  
  19.       log_warn (“Set keep alive error: %s.\n”, strerror (errno));  
  20.       return -1;  
  21.     }  
  22.   
  23.   if (WSAIoctl  
  24.       (fd, SIO_KEEPALIVE_VALS, kavars, sizeof kavars, NULL, sizeof (int), &ret, NULL,  
  25.        NULL) != 0)  
  26.     {  
  27.       log_warn (“Set keep alive error: %s.\n”, strerror (WSAGetLastError ()));  
  28.       return -1;  
  29.     }  
  30.   
  31.   return 0;  
  32. }  

sigwait函数

刚开始看sigwait函数,只是知道它是用来解除阻塞的信号,可是使我疑惑的是那么解除了以后为什么线程收到终止信号SIGINT的时候还是没能终止呢?于是网上找了一些资料,总的理解如下所示:

sigwait(&set, signo)监听信号集set中所包含的信号,并将其存在signo中。
   注意:sigwait函数所监听的信号在之前必须被阻塞。sigwait函数将阻塞调用他的线程,直到收到它所监听的信号发生了,然后sigwait将其从未决队列中取出(因为被阻塞了,所以肯定是未决了),但是有一点需要注意的是:它从未决队列取出之后,并不影响那个被取出的信号原来被阻塞的状态。它所做的工作只有两个:第一,监听被阻塞的信号;第二,如果所监听的信号产生了,则将其从未决队列中移出来(这里实时信号和非实时信号又有区别,体现在取出的顺序等,具体自己取网上查,这里不再详述)。在一些帖子中看到:sigwait取出未决信号之后,并将其原来的阻塞状态转为非阻塞状态,这是严重错误的,sigwait并不改变信号的阻塞与非阻塞状态,它只做上面的两个工作。(以上描述有错的话,欢迎指正)

多线程程序与fork()

多线程程序里不准使用fork

UNIX上C++程序设计守则3

准则3:多线程程序里不准使用fork

在多线程程序里,在”自身以外的线程存在的状态”下一使用fork的话,就可能引起各种各样的问题.比较典型的例子就是,fork出来的子进程可能会死锁.请不要,在不能把握问题的原委的情况下就在多线程程序里fork子进程.

能引起什么问题呢?

那看看实例吧.一执行下面的代码,在子进程的执行开始处调用doit()时,发生死锁的机率会很高.

1void* doit(void*) {
2
3    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
4
5    pthread_mutex_lock(&mutex);
6
7    struct timespec ts = {10, 0}; nanosleep(&ts, 0); // 10秒寝る
8                                                     // 睡10秒
9
10    pthread_mutex_unlock(&mutex);
11
12    return 0;
13
14}
15
16 
17
18int main(void) {
19
20pthread_t t; 
21
22pthread_create(&t, 0, doit, 0); 
23
24                                // 做成并启动子线程
25
26    if (fork() == 0) {
27
28        
29
30        
31
32        //子进程
33
34        //在子进程被创建的瞬间,父的子进程在执行nanosleep的场合比较多
35
36        doit(0); return 0;
37
38    }
39
40pthread_join(t, 0); //
41
42                    // 等待子线程结束
43
44}
45


以下是说明死锁的理由.

一般的,fork做如下事情
   1. 父进程的内存数据会原封不动的拷贝到子进程中
   2. 子进程在单线程状态下被生成

在内存区域里,静态变量*2mutex的内存会被拷贝到子进程里.而且,父进程里即使存在多个线程,但它们也不会被继承到子进程里. fork的这两个特征就是造成死锁的原因.
译者注: 死锁原因的详细解释 —
    1. 线程里的doit()先执行.
    2. doit执行的时候会给互斥体变量mutex加锁.
    3. mutex变量的内容会原样拷贝到fork出来的子进程中(在此之前,mutex变量的内容已经被线程改写成锁定状态).
    4. 子进程再次调用doit的时候,在锁定互斥体mutex的时候会发现它已经被加锁,所以就一直等待,直到拥有该互斥体的进程释放它(实际上没有人拥有这个mutex锁).
    5. 线程的doit执行完成之前会把自己的mutex释放,但这是的mutex和子进程里的mutex已经是两份内存.所以即使释放了mutex锁也不会对子进程里的mutex造成什么影响.

例如,请试着考虑下面那样的执行流程,就明白为什么在上面多线程程序里不经意地使用fork就造成死锁了*3.
1.    在fork前的父进程中,启动了线程1和2
2.    线程1调用doit函数
3.    doit函数锁定自己的mutex
4.    线程1执行nanosleep函数睡10秒
5.    在这儿程序处理切换到线程2
6.    线程2调用fork函数
7.    生成子进程
8.    这时,子进程的doit函数用的mutex处于”锁定状态”,而且,解除锁定的线程在子进程里不存在
9.    子进程的处理开始
10.   子进程调用doit函数
11.   子进程再次锁定已经是被锁定状态的mutex,然后就造成死锁

像这里的doit函数那样的,在多线程里因为fork而引起问题的函数,我们把它叫做”fork-unsafe函数”.反之,不能引起问题的函数叫做”fork-safe函数”.虽然在一些商用的UNIX里,源于OS提供的函数(系统调用),在文档里有fork-safety的记载,但是在Linux(glibc)里当然!不会被记载.即使在POSIX里也没有特别的规定,所以那些函数是fork-safe的,几乎不能判别.不明白的话,作为unsafe考虑的话会比较好一点吧.(2004/9/12追记)Wolfram Gloger说过,调用异步信号安全函数是规格标准,所以试着调查了一下,在pthread_atforkの这个地方里有” In the meantime*5, only a short list of async-signal-safe library routines are promised to be available.”这样的话.好像就是这样.

随便说一下,malloc函数就是一个维持自身固有mutex的典型例子,通常情况下它是fork-unsafe的.依赖于malloc函数的函数有很多,例如printf函数等,也是变成fork-unsafe的.

直到目前为止,已经写上了thread+fork是危险的,但是有一个特例需要告诉大家.”fork后马上调用exec的场合,是作为一个特列不会产生问题的”. 什么原因呢..? exec函数*6一被调用,进程的”内存数据”就被临时重置成非常漂亮的状态.因此,即使在多线程状态的进程里,fork后不马上调用一切危险的函数,只是调用exec函数的话,子进程将不会产生任何的误动作.但是,请注意这里使用的”马上”这个词.即使exec前仅仅只是调用一回printf(“I’m child process”),也会有死锁的危险.
译者注:exec函数里指明的命令一被执行,改命令的内存映像就会覆盖父进程的内存空间.所以,父进程里的任何数据将不复存在.

如何规避灾难呢?
为了在多线程的程序中安全的使用fork,而规避死锁问题的方法有吗?试着考虑几个.

规避方法1:做fork的时候,在它之前让其他的线程完全终止.
在fork之前,让其他的线程完全终止的话,则不会引起问题.但这仅仅是可能的情况.还有,因为一些原因而其他线程不能结束就执行了fork的时候,就会是产生出一些解析困难的不具合的问题.

规避方法2:fork后在子进程中马上调用exec函数

(2004/9/11 追记一些忘了写的东西)
不用使用规避方法1的时候,在fork后不调用任何函数(printf等)就马上调用execl等,exec系列的函数.如果在程序里不使用”没有exec就fork”的话,这应该就是实际的规避方法吧.
译者注:笔者的意思可能是把原本子进程应该做的事情写成一个单独的程序,编译成可执行程序后由exec函数来调用.

规避方法3:”其他线程”中,不做fork-unsafe的处理 
除了调用fork的线程,其他的所有线程不要做fork-unsafe的处理.为了提高数值计算的速度而使用线程的场合*7,这可能是fork-safe的处理,但是在一般的应用程序里则不是这样的.即使仅仅是把握了那些函数是fork-safe的,做起来还不是很容易的.fork-safe函数,必须是异步信号安全函数,而他们都是能数的过来的.因此,malloc/new,printf这些函数是不能使用的.

规避方法4:使用pthread_atfork函数,在即将fork之前调用事先准备的回调函数.
使用pthread_atfork函数,在即将fork之前调用事先准备的回调函数,在这个回调函数内,协商清除进程的内存数据.但是关于OS提供的函数(例:malloc),在回调函数里没有清除它的方法.因为malloc里使用的数据结构在外部是看不见的.因此,pthread_atfork函数几乎是没有什么实用价值的.

规避方法5:在多线程程序里,不使用fork
就是不使用fork的方法.即用pthread_create来代替fork.这跟规避策2一样都是比较实际的方法,值得推荐.

线程 fork之后

fork函数调用会创建子进程,子进程的地址空间是在调用fork时父进程地址空间的拷贝。因为子进程地址空间跟父进程一样,所以调用fork时,子进程继承了父进程中的所有互斥锁、读写锁和条件变量(包括它们的状态)。
但在多线程环境中,调用fork时,子进程中只有一个线程存在,这个线程是调用fork函数的那个线程,其他线程都没有被拷贝。
根据上述两点,子进程中的锁可能被不存在的线程所拥有,这样子进程将没法获取或释放这些锁。针对这个问题有一个解决办法,即在调用fork之前,线程先获取进程中所有锁,在调用fork后分别在父子进程中释放这些锁,从而可以重新利用这些资源。因为fork之前,当前线程拥有所有的锁,所以fork之后,当前线程继续存在,子进程可以安全的释放这些锁。
当然,在调用fork后,子进程马上调用exec,就无需考虑这些问题了,因为子进程地址空间被完全更换了。
函数pthread_atfork专门用来解决这种问题:
int pthread_atfork ( void (*prepare)(void), void (*parent)(void), void (*child)(void) );
pthread_atfork安装一些在fork调用时的回调函数。prepare函数将在fork创建子进程之前被调用,通常可以用来获取进程中的所有锁;parent在fork创建子进程后返回前在父进程中被调用,可以用来释放父进程中的锁;child在fork创建子进程后fork返回前在子进程中被调用,可以用来释放子进程中的锁。给这三个参数传递NULL,表示不调用该函数。
可以调用pthread_atfork多次注册多组回调函数,这时,回调函数调用的顺序规定如下:
①prepare函数调用顺序与它们的注册顺序相反;
②parent和child函数的调用顺序与注册顺序相同。