多线程同步与信号机制

操作系统进程控制实验:fork 与进程间通信

一、实验一:fork 创建多个进程(标准输出加锁)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <unistd.h>

int main() {
int p1, p2, i;
// 创建子进程p1
while ((p1 = fork()) == -1);
if (p1 == 0) {
lockf(1, 1, 0); // 锁定标准输出(描述符1)
for (i = 0; i < 10; i++)
printf("daughter %d\n", i);
lockf(1, 0, 0); // 解锁
} else {
// 创建子进程p2
while ((p2 = fork()) == -1);
if (p2 == 0) {
lockf(1, 1, 0); // 加锁
for (i = 0; i < 10; i++)
printf("son %d\n", i);
lockf(1, 0, 0); // 解锁
} else {
lockf(1, 1, 0); // 加锁
for (i = 0; i < 10; i++)
printf(" parent %d\n", i);
lockf(1, 0, 0); // 解锁
}
}
return 0;
}

运行结果

img

结果分析:

  • 进程关系:父进程创建子进程p1(daughter)和p2(son)。
  • 加锁机制:通过lockf(1, 1, 0)锁定标准输出,确保各进程输出不交叉,顺序为: daughter 0-9son 0-9parent 0-9(因父进程在子进程之后执行)。

二、实验二:fork 与文件加锁(写入文件)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

int main() {
int p1, p2, i;
FILE *fp;
fp = fopen("to_be_locked.txt", "w+"); // 打开文件
if (fp == NULL) {
printf("Fail to create file");
exit(-1);
}
// 创建子进程p1
while ((p1 = fork()) == -1);
if (p1 == 0) {
lockf(fileno(fp), 1, 0); // 锁定文件描述符
for (i = 0; i < 10; i++)
fprintf(fp, "daughter %d\n", i);
lockf(fileno(fp), 0, 0); // 解锁
} else {
// 创建子进程p2
while ((p2 = fork()) == -1);
if (p2 == 0) {
lockf(fileno(fp), 1, 0); // 加锁
for (i = 0; i < 10; i++)
fprintf(fp, "son %d\n", i);
lockf(fileno(fp), 0, 0); // 解锁
} else {
wait(NULL); // 等待子进程完成
lockf(fileno(fp), 1, 0); // 加锁
for (i = 0; i < 10; i++)
fprintf(fp, "parent %d\n", i);
lockf(fileno(fp), 0, 0); // 解锁
}
}
fclose(fp);
return 0;
}

运行结果

img

结果分析:

  • 文件加锁:通过lockf(fileno(fp), 1, 0)锁定文件,确保写入顺序为: son 0-9daughter 0-9parent 0-9(父进程通过wait(NULL)等待子进程完成后执行)。
  • 与实验一差异:输出顺序不同,因子进程p1p2的执行顺序由调度器决定,体现进程并发特性。

三、实验三:进程信号处理(kill 与 signal)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

void waiting(); // 等待函数
void stop(); // 信号处理函数
int wait_mark; // 等待标志

int main() {
int p1, p2;
// 创建子进程p1
while ((p1 = fork()) == -1);
if (p1 > 0) {
// 创建子进程p2
while ((p2 = fork()) == -1);
if (p2 > 0) {
wait_mark = 1;
signal(SIGINT, stop); // 捕获Ctrl+C信号
waiting(); // 等待子进程响应信号
kill(p1, 16); // 向p1发送自定义信号16
kill(p2, 17); // 向p2发送自定义信号17
wait(0); wait(0); // 等待子进程退出
printf("Parent process is killed!\n");
exit(0);
} else {
wait_mark = 1;
signal(17, stop); // 捕获信号17
signal(SIGINT, SIG_IGN); // 忽略Ctrl+C
waiting();
lockf(1, 1, 0);
printf("Child process 2 is killed by parent!\n");
lockf(1, 0, 0);
exit(0);
}
} else {
wait_mark = 1;
signal(16, stop); // 捕获信号16
signal(SIGINT, SIG_IGN); // 忽略Ctrl+C
waiting();
lockf(1, 1, 0);
printf("Child process 1 is killed by parent!\n");
lockf(1, 0, 0);
exit(0);
}
}

void waiting() {
while (wait_mark != 0); // 循环等待信号
}

void stop() {
wait_mark = 0; // 接收到信号后修改标志
}

运行结果

img

结果分析:

  • 信号机制

    • 父进程通过kill(p1, 16)kill(p2, 17)向子进程发送自定义信号。
    • 子进程通过signal(16, stop)signal(17, stop)注册信号处理函数,接收到信号后修改wait_mark,结束等待循环。
  • 输出顺序

    1. 用户按下Ctrl+C(SIGINT),父进程捕获后触发stop(),向子进程发送信号。
    2. 子进程接收到信号后输出提示并退出,最后父进程输出Parent process is killed!

四、关键知识点总结

  1. fork () 函数
    • 创建子进程,返回值为0(子进程)或子进程 PID(父进程),失败返回-1
    • 子进程复制父进程的地址空间,两者并发执行。
  2. 进程同步与加锁
    • lockf(fd, mode, size)用于锁定文件或标准输出,避免多个进程同时写入导致内容混乱。
    • mode=1为加锁,mode=0为解锁,size为锁定字节数(0表示锁定整个文件)。
  3. 信号处理
    • signal(sig, handler)注册信号处理函数,如SIGINT(Ctrl+C)、自定义信号(如1617)。
    • kill(pid, sig)向进程发送信号,用于进程间通信和控制。
  4. wait () 函数
    • 父进程通过wait(NULL)等待子进程结束,回收僵尸进程资源。

多线程同步与信号机制
http://example.com/2024/10/23/多线程同步与信号机制/
作者
John Doe
发布于
2024年10月23日
许可协议