- 浏览: 142426 次
文章分类
最新评论
父进程调用 fork 创建的子进程会继承整个地址空间的副本,以及每个互斥量、读写锁和条件变量的状态。如果父进程包含一个以上的线程,子进程在 fork 返回后,如果不是紧接着调用 exec 的话,就需要清理锁状态。因为在子进程内部,只存在父进程中调用 fork 的线程的副本一个线程。如果父进程中的线程占有锁,子进程将同样占有这些锁。问题是子进程可能并不包含占有锁的线程的副本,所以它就没法知道它占有了哪些锁、需要释放哪些锁。
因此在多线程的进程中,为了避免不一致状态的问题,POSIX.1 声明,在 fork 返回和子进程调用 exec 族函数之间,子进程只能调用异步信号安全的函数。这就限制了在调用 exec 之前子进程能做什么,但不涉及锁状态的问题。要清除锁状态,可以调用 pthread_atfork 函数建立 fork 处理程序(对于条件变量,由于它可能是使用全局锁来保护,也可能是直接把锁嵌入到条件变量的数据结构中,目前还没有可移植的方法对这样的锁进行状态清理)。
使用该函数一次最多可以安装 3 个清理锁的函数。其中 prepare 函数由父进程在 fork 创建子进程前调用,用来获取父进程定义的所有锁。parent 函数是在 fork 创建子进程之后、返回之前在父进程上下文中调用的,用来对 prepare 获取的所有锁进行解锁。child 函数则是在 fork 返回之前在子进程上下文中调用,用来释放 prepare 获取的所有锁。
可以多次调用该函数来设置多套 fork 处理程序,对不需要的某个处理程序可以在对应位置传入空指针。多个 fork 处理程序的调用顺序是不相同的。parent 和 child 是以它们注册时的顺序调用,而 prepare 的调用则与注册顺序相反。这样可以允许多个模块注册它们自己的 fork 处理程序,而且可以保持锁的层次。例如,假设模块 A 调用模块 B 中的函数,而且每个模块有自己的一套锁。如果锁的层次是 A 在 B 之前,则模块 B 必须在模块 A 之前设置它的 fork 处理程序。当父进程调用 fork 时,就会执行以下的步骤(假设子进程在父进程之前执行):
(1)调用模块 A 的 prepare 来获取模块 A 的所有锁。
(2)调用模块 B 的 prepare 来获取模块 B 的所有锁。
(3)创建子进程。
(4)调用模块 B 中的 child 处理程序来释放子进程中模块 B 的所有锁。
(5)调用模块 A 中的 child 处理程序来释放子进程中模块 A 的所有锁。
(6)fork 函数返回到子进程。
(7)调用模块 B 中的 parent 处理程序来释放父进程中模块 B 的所有锁。
(8)调用模块 A 中的 parent 处理程序来释放父进程中模块 A 的所有锁。
(9)fork 函数返回到父进程。
下面的程序描述了如何使用 pthread_atfork 和 fork 处理程序。
运行结果如下:
由此可见 prepare 处理程序在调用 fork 之后运行,child 在调用返回到子进程之前执行,parent 在 fork 调用返回给父进程之前运行。
虽然 pthread_atfork 的意图是使 fork 之后的锁状态保持一致,但它还是存在下面这些不足之处,因此只能在有限情况下可用。
(1)没有很好的办法对较复杂的同步对象(如条件变量和屏障)进行状态的重新初始化。
(2)某些错误检查的互斥量实现在 child fork 处理程序试图对被父进程加锁的互斥量进行解锁时会产生错误。
(3)递归互斥量不能在 child fork 处理程序中清理,因为没法确定加锁的次数。
(4)如果子进程只允许调用异步信号安全的函数,child fork 处理程序就不可能清理同步对象,因为用于操作清理的所有函数都不是异步信号安全的。实际的问题是同步对象在某个线程调用 fork 时可能处于中间状态,除非同步对象处于一致状态,否则无法被清理。
(5)如果应用程序在信号处理程序中调用了 fork(这是合法的,因为 fork 本身是异步信号安全的),则 pthread_atfork 注册的 fork 处理程序只能调用异步信号安全的函数,否则结果将是未定义的。
因此在多线程的进程中,为了避免不一致状态的问题,POSIX.1 声明,在 fork 返回和子进程调用 exec 族函数之间,子进程只能调用异步信号安全的函数。这就限制了在调用 exec 之前子进程能做什么,但不涉及锁状态的问题。要清除锁状态,可以调用 pthread_atfork 函数建立 fork 处理程序(对于条件变量,由于它可能是使用全局锁来保护,也可能是直接把锁嵌入到条件变量的数据结构中,目前还没有可移植的方法对这样的锁进行状态清理)。
#include <pthread.h> int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)); /* 返回值:若成功,返回 0;否则,返回错误编号 */
使用该函数一次最多可以安装 3 个清理锁的函数。其中 prepare 函数由父进程在 fork 创建子进程前调用,用来获取父进程定义的所有锁。parent 函数是在 fork 创建子进程之后、返回之前在父进程上下文中调用的,用来对 prepare 获取的所有锁进行解锁。child 函数则是在 fork 返回之前在子进程上下文中调用,用来释放 prepare 获取的所有锁。
可以多次调用该函数来设置多套 fork 处理程序,对不需要的某个处理程序可以在对应位置传入空指针。多个 fork 处理程序的调用顺序是不相同的。parent 和 child 是以它们注册时的顺序调用,而 prepare 的调用则与注册顺序相反。这样可以允许多个模块注册它们自己的 fork 处理程序,而且可以保持锁的层次。例如,假设模块 A 调用模块 B 中的函数,而且每个模块有自己的一套锁。如果锁的层次是 A 在 B 之前,则模块 B 必须在模块 A 之前设置它的 fork 处理程序。当父进程调用 fork 时,就会执行以下的步骤(假设子进程在父进程之前执行):
(1)调用模块 A 的 prepare 来获取模块 A 的所有锁。
(2)调用模块 B 的 prepare 来获取模块 B 的所有锁。
(3)创建子进程。
(4)调用模块 B 中的 child 处理程序来释放子进程中模块 B 的所有锁。
(5)调用模块 A 中的 child 处理程序来释放子进程中模块 A 的所有锁。
(6)fork 函数返回到子进程。
(7)调用模块 B 中的 parent 处理程序来释放父进程中模块 B 的所有锁。
(8)调用模块 A 中的 parent 处理程序来释放父进程中模块 A 的所有锁。
(9)fork 函数返回到父进程。
下面的程序描述了如何使用 pthread_atfork 和 fork 处理程序。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER; void prepare(void){ printf("prepare acquiring locks\n"); pthread_mutex_lock(&lock1); pthread_mutex_lock(&lock2); } void parent(void){ printf("parent unlocking locks\n"); pthread_mutex_unlock(&lock1); pthread_mutex_unlock(&lock2); } void child(void){ printf("child unlocking locks\n"); pthread_mutex_unlock(&lock1); pthread_mutex_unlock(&lock2); } void *thr_fn(void *arg){ printf("thread started\n"); pause(); printf("thread ended\n"); // can't reach here, for no signal handlers. return (void *)0; } int main(void){ if(pthread_atfork(prepare, parent, child) != 0){ printf("pthread_atfork error\n"); exit(1); } pthread_t tid; if(pthread_create(&tid, NULL, thr_fn, NULL) != 0){ printf("pthread_create error\n"); exit(1); } sleep(2); // Not reliably waiting for thread to run. printf("parent process about to call fork()...\n"); pid_t pid; if((pid = fork()) < 0){ printf("fork error\n"); exit(1); } if(pid == 0) printf("child returned from fork\n"); else printf("parent returned from fork\n"); exit(0); }
运行结果如下:
$ ./atforkDemo.out thread started parent process about to call fork()... prepare acquiring locks parent unlocking locks parent returned from fork child unlocking locks child returned from fork
由此可见 prepare 处理程序在调用 fork 之后运行,child 在调用返回到子进程之前执行,parent 在 fork 调用返回给父进程之前运行。
虽然 pthread_atfork 的意图是使 fork 之后的锁状态保持一致,但它还是存在下面这些不足之处,因此只能在有限情况下可用。
(1)没有很好的办法对较复杂的同步对象(如条件变量和屏障)进行状态的重新初始化。
(2)某些错误检查的互斥量实现在 child fork 处理程序试图对被父进程加锁的互斥量进行解锁时会产生错误。
(3)递归互斥量不能在 child fork 处理程序中清理,因为没法确定加锁的次数。
(4)如果子进程只允许调用异步信号安全的函数,child fork 处理程序就不可能清理同步对象,因为用于操作清理的所有函数都不是异步信号安全的。实际的问题是同步对象在某个线程调用 fork 时可能处于中间状态,除非同步对象处于一致状态,否则无法被清理。
(5)如果应用程序在信号处理程序中调用了 fork(这是合法的,因为 fork 本身是异步信号安全的),则 pthread_atfork 注册的 fork 处理程序只能调用异步信号安全的函数,否则结果将是未定义的。
发表评论
-
打开伪终端设备
2018-07-09 20:50 1252在伪终端概述一节中已对 PTY进行了初步的介绍。尽管 ... -
伪终端概述
2018-06-02 11:05 1550伪终端就是指,一个应用程序看上去像一个终端,但事实上它 ... -
终端窗口大小和 termcap
2018-05-29 22:39 800多数 UNIX 系统都提供了一种跟踪当前终端窗口大小的 ... -
终端规范模式和非规范模式
2018-05-29 00:25 950终端规范模式很简单:发一个读请求,当一行已经输入后,终 ... -
终端标识
2018-05-23 11:18 569尽管控制终端的名字在多数 UNIX 系统上都是 /de ... -
波特率和行控制函数
2018-05-22 07:53 944虽然大多数终端设 ... -
终端属性和选项标志
2018-05-20 07:40 710tcgetattr 和 tcsetattr ... -
终端特殊输入字符
2018-05-17 06:33 815终端支持下表所示的特殊输入字符。 为了更改 ... -
终端 I/O 综述
2018-05-10 07:56 439终端设备可认为是由内核中的终端驱动程序控制的。每个终端 ... -
POSIX 信号量
2018-05-09 00:03 579在XSI IPC通信之信 ... -
XSI IPC 通信之共享存储
2018-04-25 07:18 947在XSI IPC通信之消息队列和XSI IPC通信之信 ... -
XSI IPC通信之信号量
2018-04-17 23:38 617在XSI IPC通信之消 ... -
XSI IPC通信之消息队列
2018-04-15 10:54 497消息队列是消息的链接表,存储在内核中,由消息队列标识符 ... -
XSI IPC 相似特征介绍
2018-02-08 23:48 485有 3 种称作 XSI IPC ... -
IPC 通信之 FIFO
2018-02-06 22:55 421FIFO 也被称为命名管道,未命名的管道只能在两个相关 ... -
IPC 通信之管道
2018-01-30 22:22 389管道是 UNIX 系统 IPC 的最古老但也是最常用的 ... -
readv/writev 函数及存储映射 I/O
2018-01-19 00:57 889readv 和 writev 函数可用于在一次函数调用 ... -
POSIX 异步 I/O
2018-01-16 21:33 455POSIX 异步 I/O 接口为对不同类型的文件进行异 ... -
fcntl 记录锁
2018-01-06 23:48 615记录锁的功能是:当有进程正在读或修改文件的某个部分时, ... -
守护进程惯例
2018-01-06 23:52 439UNIX 系统中,守护进程遵循下列通用惯例。 ...
相关推荐
### Linux 下进程、线程与 fork 的深入理解 #### 题目背景及解析 本篇文章将基于一道经典的面试题目来探讨 Linux 下进程创建机制,特别是 `fork` 函数的工作原理。该题目不仅考验应试者对进程创建的理解,还涉及了...
9. **线程和fork**:`fork`创建子进程时,子进程继承父进程的所有线程,但通常只执行`fork`后的那个线程,其他线程的状态不确定,需谨慎处理。 以上内容涵盖了Unix环境下线程控制的主要方面,包括基本操作、属性...
线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
·使用多线程和fork选项。 ·使用网络设备从一个进程获取另一个进程的信息。 ·创建可点击的、易于交互的GUI工具。 ·通过交互式SNMP编程实现监控大型主机集群。 ·掌握IPython shell,作为Bash、Korn或Z-Shell的...
使用多线程和fork选项。 使用网络设备从一个进程获取另一个进程的信息。 创建可点击的、易于交互的GUl工具。 通过交互式SNMP编程实现监控大型主机集群。 掌握IPython shell,作为Bash、Korn或Z—Shell的替换或补充。...
·使用多线程和fork选项。 ·使用网络设备从一个进程获取另一个进程的信息。 ·创建可点击的、易于交互的gui工具。 ·通过交互式snmp编程实现监控大型主机集群。 ·掌握ipython shell,作为bash、korn或z-shell的...
·使用多线程和fork选项。 ·使用网络设备从一个进程获取另一个进程的信息。 ·创建可点击的、易于交互的gui工具。 ·通过交互式snmp编程实现监控大型主机集群。 ·掌握ipython shell,作为bash、korn或z-shell的...
, 使用多线程和fork选项。, 使用网络设备从一个进程获取另一个进程的信息。, 创建可点击的、易于交互的GUl工具。, 通过交互式SNMP编程实现监控大型主机集群。, 掌握IPython shell,作为Bash、Korn或Z—Shell的替换或...
Java的并发库提供了线程和Fork/Join框架,适合进行更复杂的并行任务管理。 3. **设计分析** - **串行算法设计**:首先,学生需要设计和实现一个基础的串行算法,作为并行版本的基准,用于后续的性能比较。 - **...
- **线程和fork**:探讨在多线程环境中调用`fork`函数的影响。 - **线程和I/O**:在线程中进行I/O操作时应注意的问题。 #### 第十三章 守护进程 - **设计良好的守护进程的规则**:包括脱离终端、成为会话领导等...
12.9 线程和fork 12.10 线程和I/O 12.11 小结 习题 第13章 守护进程 13.1 引言 13.2 守护进程的特征 13.3 编程规则 13.4 出错记录 13.5 单实例守护进程 13.6 守护进程的惯例 13.7...
线程和fork** `fork`函数在多线程环境下需要注意同步问题。 **8. 线程和I/O** 线程安全的I/O函数。 #### 第十三章 守护进程 **1. 设计一个良好的守护进程的一般编程规则** 守护进程需要脱离控制终端,忽略大多数...
在Nachos中,通过添加新的线程状态`STATIC_READY`和`STATIC_BLOCKED`,以及`Thread::suspend()`和`Thread::active()`方法来实现线程的挂起和激活。挂起线程时,根据线程当前状态将其状态改为`STATIC_READY`或`STATIC...
Fork/Join框架特别适合处理可以递归拆分的计算密集型任务,比如大数据集的搜索、排序和其他计算。 首先,Fork/Join框架的基本操作包括“fork”和“join”两个动作。fork操作是指将一个大任务拆分为多个可以并行处理...
线程和fork** - `fork()`在多线程环境中可能导致问题。 **8. 线程和I/O** - 线程间的I/O操作需要注意同步。 #### 第十三章 守护进程 **1. 设计一个良好的守护进程的一般编程规则** - 守护进程应该脱离终端,重新...
12.9 线程和fork 336 12.10 线程和I/O 339 12.11 小结 340 习题 340 第13章 守护进程 341 13.1 引言 341 13.2 守护进程的特征 341 13.3 编程规则 342 13.4 出错记录 345 13.5 单实例守护进程 348...
313 12.3 线程属性 314 12.4 同步属性 318 12.5 重入 324 12.6 线程私有数据 328 12.7 取消选项 331 12.8 线程和信号 333 12.9 线程和fork 336 12.10 线程和I/O 339 12.11 小结 340 习题 340 第...
文档内容涵盖了如何使用GDB进行源代码级调试的各个方面,包括但不限于如何启动和退出GDB,如何在GDB中运行程序,如何使用断点、观察点和捕获点,以及如何在GDB中处理多进程、多线程和fork的调试。这些内容对于开发者...
12.9 线程和fork 336 12.10 线程和i/o 339 12.11 小结340 习题340 第13章守护进程341 13.1 引言341 13.2 守护进程的特征341 13.3 编程规则342 13.4 出错记录345 13.5 单实例守护进程348 13.6 守护进程的...