父子进程间交互执行是指用一种同步原语,实现父进程和子进程在某一时刻只有一个进程执行,之后由另外一个进程执行,用一段代码举例如下:

  1. SYNC_INIT();
  2.  
  3. int i=, counter=;
  4. pid_t pid = fork ();
  5. if (pid < )
  6. err_sys ("fork error");
  7. else if (pid > )
  8. {
  9. // parent
  10. for (i=; i<NLOOPS; i+=)
  11. {
  12. counter = update ((long *)area);
  13. if (counter != i)
  14. err_quit ("parent: expected %d, got %d", i, counter);
  15. else
  16. printf ("parent increase to %d based %d\n", i+, counter);
  17.  
  18. SYNC_TELL(pid, );
  19. SYNC_WAIT();
  20. }
  21.  
  22. printf ("parent exit\n");
  23. }
  24. else
  25. {
  26. for (i=; i<NLOOPS+; i+=)
  27. {
  28. SYNC_WAIT();
  29. counter = update ((long *)area);
  30. if (counter != i)
  31. err_quit ("child: expected %d, got %d", i, counter);
  32. else
  33. printf ("child increase to %d based %d\n", i+, counter);
  34.  
  35. SYNC_TELL(getppid (), );
  36. }
  37.  
  38. printf ("child exit\n");
  39. }

其中area是指向共享内存的一个地址,update用来增加area指向的内容(为long),在fork之后,父子进程交替更新此值。

它们使用了一些抽象的同步原语,例如SYNC_INIT用于初始化同步设施、SYNC_WAIT等待另外进程的信号、SYNC_TELL向另外进程发送信号。

下面是成功同步后的输出(假设NLOOPS为100):

  1. create shared-memory 3801126 with size 4 ok
  2. attach shared-memory at 0xb7733000
  3. parent increase to 1 based 0
  4. child increase to 2 based 1
  5. parent increase to 3 based 2
  6. child increase to 4 based 3
  7. parent increase to 5 based 4
  8. child increase to 6 based 5
  9. parent increase to 7 based 6
  10. child increase to 8 based 7
  11. parent increase to 9 based 8
  12. child increase to 10 based 9
  13. parent increase to 11 based 10
  14. child increase to 12 based 11
  15. parent increase to 13 based 12
  16. child increase to 14 based 13
  17. parent increase to 15 based 14
  18. child increase to 16 based 15
  19. parent increase to 17 based 16
  20. child increase to 18 based 17
  21. parent increase to 19 based 18
  22. child increase to 20 based 19
  23. parent increase to 21 based 20
  24. child increase to 22 based 21
  25. parent increase to 23 based 22
  26. child increase to 24 based 23
  27. parent increase to 25 based 24
  28. child increase to 26 based 25
  29. parent increase to 27 based 26
  30. child increase to 28 based 27
  31. parent increase to 29 based 28
  32. child increase to 30 based 29
  33. parent increase to 31 based 30
  34. child increase to 32 based 31
  35. parent increase to 33 based 32
  36. child increase to 34 based 33
  37. parent increase to 35 based 34
  38. child increase to 36 based 35
  39. parent increase to 37 based 36
  40. child increase to 38 based 37
  41. parent increase to 39 based 38
  42. child increase to 40 based 39
  43. parent increase to 41 based 40
  44. child increase to 42 based 41
  45. parent increase to 43 based 42
  46. child increase to 44 based 43
  47. parent increase to 45 based 44
  48. child increase to 46 based 45
  49. parent increase to 47 based 46
  50. child increase to 48 based 47
  51. parent increase to 49 based 48
  52. child increase to 50 based 49
  53. parent increase to 51 based 50
  54. child increase to 52 based 51
  55. parent increase to 53 based 52
  56. child increase to 54 based 53
  57. parent increase to 55 based 54
  58. child increase to 56 based 55
  59. parent increase to 57 based 56
  60. child increase to 58 based 57
  61. parent increase to 59 based 58
  62. child increase to 60 based 59
  63. parent increase to 61 based 60
  64. child increase to 62 based 61
  65. parent increase to 63 based 62
  66. child increase to 64 based 63
  67. parent increase to 65 based 64
  68. child increase to 66 based 65
  69. parent increase to 67 based 66
  70. child increase to 68 based 67
  71. parent increase to 69 based 68
  72. child increase to 70 based 69
  73. parent increase to 71 based 70
  74. child increase to 72 based 71
  75. parent increase to 73 based 72
  76. child increase to 74 based 73
  77. parent increase to 75 based 74
  78. child increase to 76 based 75
  79. parent increase to 77 based 76
  80. child increase to 78 based 77
  81. parent increase to 79 based 78
  82. child increase to 80 based 79
  83. parent increase to 81 based 80
  84. child increase to 82 based 81
  85. parent increase to 83 based 82
  86. child increase to 84 based 83
  87. parent increase to 85 based 84
  88. child increase to 86 based 85
  89. parent increase to 87 based 86
  90. child increase to 88 based 87
  91. parent increase to 89 based 88
  92. child increase to 90 based 89
  93. parent increase to 91 based 90
  94. child increase to 92 based 91
  95. parent increase to 93 based 92
  96. child increase to 94 based 93
  97. parent increase to 95 based 94
  98. child increase to 96 based 95
  99. parent increase to 97 based 96
  100. child increase to 98 based 97
  101. parent increase to 99 based 98
  102. child increase to 100 based 99
  103. child exit
  104. parent exit
  105. remove that shared-memory

这套同步原语可以有多种实现方案,简单如管道、xsi信号量,甚至直接使用信号。下面是一些例子:

1. 使用管道

  1. #ifdef USE_PIPE_SYNC
  2.  
  3. // pp is the pipe that parent notify(write) child wait(read)
  4. // pc is the pipe that child notify(write) parent wait(read)
  5. static int pp[], pc[];
  6.  
  7. void SYNC_INIT (void)
  8. {
  9. if (pipe (pp) < || pipe(pc) < )
  10. err_sys ("pipe error");
  11. }
  12.  
  13. void SYNC_TELL (pid_t pid, int child)
  14. {
  15. // close unused read end to avoid poll receive events
  16. // note, we can NOT do it in SYNC_INIT,
  17. // as at that moment, we have not fork yet !
  18. if (child) {
  19. close (pp[]);
  20. close (pc[]);
  21. pp[] = pc[] = -;
  22. } else {
  23. close (pc[]);
  24. close (pp[]);
  25. pc[] = pp[] = -;
  26. }
  27.  
  28. if (write (child ? pp[] : pc[], child ? "p" : "c", ) != )
  29. err_sys ("write error");
  30. }
  31.  
  32. void SYNC_WAIT (int child /* unused */)
  33. {
  34. int n = , m = ;
  35. struct pollfd fds[] = {{ }};
  36. // if fd==-1, just be a place taker !
  37. //if (pp[0] != -1)
  38. {
  39. fds[n].fd = pp[];
  40. fds[n].events = POLLIN;
  41. n++;
  42. }
  43.  
  44. //if (pc[0] != -1)
  45. {
  46. fds[n].fd = pc[];
  47. fds[n].events = POLLIN;
  48. n++;
  49. }
  50.  
  51. int ret = poll (fds, n, -);
  52. if (ret == -)
  53. err_sys ("poll error");
  54. else if (ret > ) {
  55. char c = ;
  56. //printf ("poll %d from %d\n", ret, n);
  57. for (m=; m<n; ++m) {
  58. //printf ("poll fd %d event 0x%08x\n", fds[m].fd, fds[m].revents);
  59. if (fds[m].revents & POLLIN) {
  60. if (fds[m].fd == pp[]) {
  61. if (read (pp[], &c, ) != )
  62. err_sys ("read parent pipe error");
  63. if (c != 'p')
  64. err_quit ("wait parent pipe but got incorrect data %c", c);
  65. }
  66. else {
  67. if (read (pc[], &c, ) != )
  68. err_sys ("read child pipe error");
  69. if (c != 'c')
  70. err_quit ("wait child pipe but got incorrect data %c", c);
  71. }
  72. }
  73. }
  74. }
  75. else
  76. printf ("poll return 0\n");
  77. }
  78.  
  79. #endif

管道的话,TELL时就是向管道写一个字节,WAIT的时候就是阻塞在对应的端读取一个字节。

注意这里WAIT没有直接使用child参数,而是使用poll同时检测两个读端,看哪个有数据就返回哪个。其实直接读对应的端更直接一些。

2.使用xsi信号量

  1. #ifdef USE_SEM_SYNC
  2.  
  3. union semun
  4. {
  5. int val; //<= value for SETVAL
  6. struct semid_ds *buf; // <= buffer for IPC_STAT & IPC_SET
  7. unsigned short int *array;// <= array for GETALL & SETALL
  8. struct seminfo *__buf; // <= buffer for IPC_INFO
  9. };
  10.  
  11. static int semid = -;
  12.  
  13. void SYNC_INIT ()
  14. {
  15. int mode = ; // 0;
  16. int flag = IPC_CREAT;
  17. #ifdef USE_EXCL
  18. flag |= IPC_EXCL;
  19. #endif
  20.  
  21. semid = semget (IPC_PRIVATE, , flag | mode);
  22. if (semid < )
  23. err_sys ("semget for SYNC failed");
  24.  
  25. printf ("create semaphore %d for SYNC ok\n", semid);
  26.  
  27. union semun sem;
  28. //sem.val = 1;
  29. //int ret = semctl (semid, 0, SETVAL, sem);
  30. //if (ret < 0)
  31. // err_sys ("semctl to set val failed");
  32.  
  33. short arr[] = { };
  34. sem.array = arr;
  35. int ret = semctl (semid, , SETALL, sem);
  36. if (ret < )
  37. err_sys ("semctl to set all val failed");
  38.  
  39. printf ("reset all semaphores ok\n");
  40. }
  41.  
  42. void SYNC_TELL (pid_t pid, int child)
  43. {
  44. struct sembuf sb;
  45. sb.sem_op = ;
  46. sb.sem_num = child ? : ;
  47. sb.sem_flg = ; // IPC_NOWAIT, SEM_UNDO
  48. int ret = semop (semid, &sb, );
  49. if (ret < )
  50. printf ("semop to release resource failed, ret %d, errno %d\n", ret, errno);
  51. else
  52. printf ("release %d resource %d\n", sb.sem_op, ret);
  53. }
  54.  
  55. void SYNC_WAIT (int child)
  56. {
  57. struct sembuf sb;
  58. sb.sem_op = -;
  59. sb.sem_num = child ? : ;
  60. sb.sem_flg = ; // IPC_NOWAIT, SEM_UNDO
  61. int ret = semop (semid, &sb, );
  62. if (ret < )
  63. printf ("semop to require resource failed, ret %d, errno %d\n", ret, errno);
  64. else
  65. printf ("require %d resource %d\n", sb.sem_op, ret);
  66. }
  67.  
  68. #endif

xsi信号量的话,在TELL时是向对应的信号量执行V操作,释放一个资源;在WAIT时是向对应的信号量执行P操作,申请一个资源,如果申请不到,就阻塞在那里。

3.使用信号

  1. #ifdef USE_SIGNAL_SYNC
  2.  
  3. static volatile sig_atomic_t sigflag;
  4. static sigset_t newmask, oldmask, zeromask;
  5.  
  6. static void sig_usr (int signo)
  7. {
  8. sigflag = ;
  9. printf ("SIGUSR1/2 called\n");
  10. }
  11.  
  12. void SYNC_INIT ()
  13. {
  14. if (apue_signal (SIGUSR1, sig_usr) == SIG_ERR)
  15. err_sys ("signal (SIGUSR1) error");
  16. if (apue_signal (SIGUSR2, sig_usr) == SIG_ERR)
  17. err_sys ("signal (SIGUSR2) error");
  18.  
  19. sigemptyset (&zeromask);
  20. sigemptyset (&newmask);
  21. sigaddset (&newmask, SIGUSR1);
  22. sigaddset (&newmask, SIGUSR2);
  23.  
  24. if (sigprocmask (SIG_BLOCK, &newmask, &oldmask) < )
  25. err_sys ("SIG_BLOCK error");
  26. }
  27.  
  28. void SYNC_TELL (pid_t pid, int child)
  29. {
  30. kill (pid, child ? SIGUSR1 : SIGUSR2);
  31. }
  32.  
  33. void SYNC_WAIT (int child /* unused */)
  34. {
  35. while (sigflag == )
  36. sigsuspend (&zeromask);
  37.  
  38. sigflag = ;
  39. if (sigprocmask (SIG_SETMASK, &oldmask, NULL) < )
  40. err_sys ("SIG_SETMASK error");
  41. }
  42.  
  43. #endif

直接使用signal的话,这里分别使用了SIGUSR1和SIGUSR2表示父子进程,TELL操作就是激发一个信号给对方;WAIT操作就是sigsuspend在某个特定信号上,直到有信号发生才返回。

注意TELL时需要指定发送信号的进程号,所以多了一个pid参数,这个参数在之前据说的两种方法中并没有使用。这也是signal不好的一点。

然后,apue 15章最后一道习题中,要求使用文件记录锁来实现上述交互执行时,发现这是不可能完成的任务!

假设我们以加锁文件或文件中一个字节来实现WAIT,使用解锁来实现TELL,那么会发现文件记录锁有以下缺点,导致它不能胜任这个工作:

1. 文件记录锁是基于文件+进程的,当fork后产生子进程时,之前加的锁自动释放;

2. 文件记录锁对于重复施加锁于一个文件或文件中某个特定字节时,它的表现就和之前没有加锁一样,直接成功返回,不会产生阻塞效果;

对于 问题1,直接的影响就是父进程加好锁之后fork,子进程启动后却没有任何初始锁,导致父子进程同步困难。

虽然这个可以通过在子进程中重新初始化来部分的解决,但是这种问题因为有进程竞争存在,问题不严密从而不完美的;

对于 问题2,就直接导致其中一个进程在它的任务循环中,TELL另外一个进程后,再WAIT本进程的同步原语时(内部通过加锁实现),

另一个进程即使没有解锁相应的文件或字节,WAIT也直接成功返回(因为本进程已经持有该锁),从而造成其中一个进程执行多次,另一个进程没有办法插进去执行的情况(虽然两个进程也不能同时执行)。

所以结论是,对于交互执行的同步场景,管道、semaphore、signal都适用,而file lock不适用。

测试程序

各种实现

[apue] 使用文件记录锁无法实现父子进程交互执行同步的更多相关文章

  1. [11]APUE:(文件)记录锁

    [a] 概念 建议锁:在遵循相同记录锁规则的进程间生效,通常用于保证某个程序自身多个进程间的数据一致性 强制锁:意在保证所有进程间的数据一致性,但不一定有效:如不能应对先 unlink 后建立同名副本 ...

  2. linux 文件记录锁详解

    一: linux记录锁更恰当的称呼应该是范围锁,它是对文件某个范围的锁定. 关于记录锁的功能就是fcntl提供的第五个功能,具体使用如下: int fcntl(int fd, int cmd, str ...

  3. Linux进程同步之记录锁(fcntl)

    记录锁相当于线程同步中读写锁的一种扩展类型,可以用来对有亲缘或无亲缘关系的进程进行文件读与写的同步,通过fcntl函数来执行上锁操作.尽管读写锁也可以通过在共享内存区来进行进程的同步,但是fcntl记 ...

  4. inux c编程:记录锁

    记录锁的功能:当一个进程正在读或修改文件的某个部分是,它可以阻止其他进程修改同一文件区.对于这个功能阐述我认为有三点要解释的: 记录锁不仅仅可以用来同步不同进程对同一文件的操作,还可以通过对同一文件加 ...

  5. [apue] linux 文件访问权限那些事儿

    前言 说到 linux 上的文件权限,其实我们在说两个实体,一是文件,二是进程.一个进程能不能访问一个文件,其实由三部分内容决定: 文件的所有者.所在的组: 文件对所有者.组用户.其它用户设置的权限访 ...

  6. LINUX编程学习笔记(十四) 创建进程与 父子进程内存空间

    1什么是进程:进程是一个执行中的程序 执行的程序: 代码->资源->CPU 进程有很多数据维护:进程状态/进程属性 所有进程属性采用的一个树形结构体维护 ps  -a//所有进程 ps - ...

  7. ZT 父子进程共享文件描述符

    转贴自倒霉熊的博客 [linux学习笔记-2]父子进程共享文件描述符 (2009-03-02 23:03:17) 转载▼ 标签: 学习 linux 子进程 文件描述符 杂谈 分类: 学习 #inclu ...

  8. Linux--谈父子进程执行过程

    fork函数用于从已存在进程中创建一个新进程,新进程成为子进程,原进程成为父进程.这两个进程分别返回他们各自的返回值, 其中父进程的返回值是子进程的进程号,子进程则返回0,因此返回值大于0标识父进程, ...

  9. shell的父子进程

    2017年1月11日, 星期三 shell的父子进程   启动/执行方式: 当前shell:               #!/bin/bash 必须行首                        ...

随机推荐

  1. Redis学习总结(五)--Redis集群创建

    在之前我们讲到了主从,但是对于大数据量的场景下我们就需要用到集群了,让我们来了解下集群吧. 为什么需要集群 单机内存太小 redis最高可以达到10万/s 请求,如果超过该频率呢? 数据分布方式 数据 ...

  2. Oracle数据库之五 限定查询和排序显示

    五.限定查询和排序显示 5.1.限定查询 5.1.1 认识限定查询 例如:如果一张表中有 100w 条数据,一旦执行了 " SELECT * FROM 表 " 语句之后,则将在屏幕 ...

  3. 设计模式(C#)——03建造者模式

    推荐阅读:  我的CSDN  我的博客园  QQ群:704621321       当一个复杂对象由一些子对象构成,并且子对象的变化会导致复杂对象的修改.这时我们需要提供一种"封装机制&qu ...

  4. Gym - 101252H

    题意略. 思路:二分.注意当利率高且m比较小的时候,每个月的偿还可能会大于本金,所以我们二分的右边界应该要设为2 * 本金. 详见代码: #include<bits/stdc++.h> # ...

  5. Python基础 2-2 列表的实际应用场景

    引言 本章主要介绍列表在实际应用中的使用场景,多维列表(嵌套列表) 如果你需要在列表保存每个人员的一些基本信息,使用列表嵌套来保存这种信息是个不错的主意. 多维列表 列表可以根据实际情况嵌套使用,比如 ...

  6. 朋友聚会,下馆子要到哪家饭馆?——单样本T检验帮你找到答案

      聚会时,五花八门的饭馆让人眼花缭乱,应该到哪家店吃呢?除了美味的食物,良好的服务态度也是好饭馆的必备品质,如何判断一家饭馆的服务态度如何?此时可以用单样本T检验来找答案~ 让顾客对A饭馆的服务态度 ...

  7. [Error] - Windows卸载程序时,提示错误2503

    1. 打开“任务管理器” 2. 切换到“详细信息”标签页,找到explorer.exe文件,并结束它. 3. 点击“任务管理器”上的文件->运行新任务,输入explorer.ext,勾选“以系统 ...

  8. ACdream1726-A Math game+(DFS+二分)+(DFS+前缀和)

    传送门 官方题解:http://acdream.info/topic?tid=4246 参考:https://www.cnblogs.com/nowandforever/p/4492428.html ...

  9. JDBC编程之预编译SQL与防注入

    在JDBC编程中,常用Statement.PreparedStatement 和 CallableStatement三种方式来执行查询语句,其中 Statement 用于通用查询, PreparedS ...

  10. webstorm的live templates快速编辑功能,让你的css JS代码书写速度飞起来

    前言: Emmet的前身是大名鼎鼎的Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生.它使用仿CSS选择器的语法来生成代码, 大大提高了HTML/CSS代码编写的速度,比如下面 ...