linux实验6(GPT问答版)
一、Q:Linux:编写一个程序,把一个文件的内容复制到另一个文件上,即实现简单的 copy 功能。要求:只用 open ()、read ()、write () 和 close () 系统调用,程序的第一个参数是源文件,第二个参数是目的文件。
A:以下是一个使用 C 编写的简单 Linux 程序,实现文件复制功能,仅使用 open()、read()、write() 和 close() 系统调用:
#include <stdio.h>
#include <fcntl.h> // 包含 open() 函数
#include <unistd.h> // 包含 read(), write(), close() 函数
#include <stdlib.h> // 包含 exit() 函数
#define BUFFER_SIZE 1024 // 定义缓冲区大小
int main(int argc, char *argv[]) {
if (argc != 3) {
// 如果参数个数不对,提示用法并退出
fprintf(stderr, "Usage: %s <source file> <destination file>\n", argv[0]);
exit(1);
}
// 打开源文件
int source_fd = open(argv[1], O_RDONLY);
if (source_fd < 0) {
perror("Error opening source file");
exit(1);
}
// 打开目标文件(不存在则创建)
int dest_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (dest_fd < 0) {
perror("Error opening destination file");
close(source_fd);
exit(1);
}
// 缓冲区用于存储读取的数据
char buffer[BUFFER_SIZE];
ssize_t bytes_read, bytes_written;
// 循环读取源文件内容,并写入目标文件
while ((bytes_read = read(source_fd, buffer, BUFFER_SIZE)) > 0) {
bytes_written = write(dest_fd, buffer, bytes_read);
if (bytes_written != bytes_read) {
perror("Error writing to destination file");
close(source_fd);
close(dest_fd);
exit(1);
}
}
// 检查是否发生读取错误
if (bytes_read < 0) {
perror("Error reading source file");
}
// 关闭文件
close(source_fd);
close(dest_fd);
printf("File copied successfully.\n");
return 0;
}
使用说明:
- 将以上代码保存为
copy.c文件。 - 使用 GCC 编译程序:
gcc -o copy copy.c。 - 运行程序,例如:
./copy source.txt destination.txt。
程序的核心步骤:
- 检查参数:确认输入和输出文件路径是否通过命令行提供。
- 打开文件:
- 源文件以只读模式打开。
- 目标文件以写模式打开。如果文件不存在,则创建它。
- 数据传输:
- 使用一个缓冲区循环读取源文件的内容(
read()),然后写入目标文件(write())。 - 每次操作都需要检查返回值,以确保没有发生错误。
- 使用一个缓冲区循环读取源文件的内容(
- 关闭文件:
- 操作完成后,使用
close()关闭源文件和目标文件。
- 操作完成后,使用
注意事项:
- 如果目标文件已存在,程序会覆盖该文件(通过
O_TRUNC标志)。 - 文件权限是
0644,即新创建的文件默认拥有用户读写权限和其他用户的只读权限。 - 缓冲区大小为
1024,可以根据需要调整。
这样编写的程序能满足题目要求,且具有一定的鲁棒性,适合初学者学习 Linux 系统调用。
Q:解释一下用到的系统函数
A:在上述程序中,用到的系统调用函数主要有:open()、read()、write() 和 close(),下面逐一进行详细解释。
1. open()
功能:用于打开文件。
头文件:
#include <fcntl.h>
函数原型:
int open(const char *pathname, int flags, mode_t mode);
参数说明:
pathname:文件路径,表示要打开的文件。flags:打开方式,常用标志包括:O_RDONLY:以只读方式打开文件。O_WRONLY:以只写方式打开文件。O_RDWR:以读写方式打开文件。O_CREAT:如果文件不存在,则创建文件(需要指定mode参数)。O_TRUNC:如果文件已存在,则清空文件内容。O_APPEND:以追加方式写入文件。
mode:文件权限(仅在创建文件时有效,如使用了O_CREAT标志),通常以八进制表示,例如:0644:用户可读写,组用户和其他用户可读。0600:只有文件所有者可读写。
返回值:
- 成功:返回文件描述符(一个非负整数,用于后续的文件操作)。
- 失败:返回
-1,并设置errno。
例子:
int fd = open("file.txt", O_RDONLY);
if (fd < 0) {
perror("Error opening file");
}
2. read()
功能:从文件中读取数据。
头文件:
#include <unistd.h>
函数原型:
ssize_t read(int fd, void *buf, size_t count);
参数说明:
fd:文件描述符(通过open()获得)。buf:指向存储读取数据的缓冲区。count:指定读取的字节数。
返回值:
- 成功:返回实际读取到的字节数(可能小于
count,如读取到文件末尾)。 - 失败:返回
-1,并设置errno。
例子:
char buffer[1024];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read < 0) {
perror("Error reading file");
}
注意:
- 当返回值为
0时,表示已经到达文件末尾(EOF)。 - 如果读取时发生错误,例如文件不存在或权限不足,会返回
-1。
3. write()
功能:向文件中写入数据。
头文件:
#include <unistd.h>
函数原型:
ssize_t write(int fd, const void *buf, size_t count);
参数说明:
fd:文件描述符。buf:指向要写入的数据的缓冲区。count:要写入的字节数。
返回值:
- 成功:返回实际写入的字节数(通常等于
count)。 - 失败:返回
-1,并设置errno。
例子:
const char *data = "Hello, world!";
ssize_t bytes_written = write(fd, data, strlen(data));
if (bytes_written < 0) {
perror("Error writing to file");
}
注意:
- 如果目标文件空间不足,可能写入部分数据,这时返回的字节数会小于
count。 - 如果
write()返回值与count不相等,通常需要重新写入剩余的数据。
4. close()
功能:关闭文件描述符。
头文件:
#include <unistd.h>
函数原型:
int close(int fd);
参数说明:
fd:需要关闭的文件描述符。
返回值:
- 成功:返回
0。 - 失败:返回
-1,并设置errno。
例子:
if (close(fd) < 0) {
perror("Error closing file");
}
注意:
- 关闭文件描述符可以释放文件相关的资源。
- 如果不及时关闭文件描述符,可能导致资源泄漏,甚至超过系统允许的文件描述符上限。
程序中的系统调用组合解释
-
打开源文件和目标文件:
- 使用
open()以只读模式打开源文件。 - 使用
open()以写模式(带创建和截断标志)打开目标文件。
- 使用
-
读取和写入文件:
- 使用
read()从源文件中读取数据到缓冲区。 - 使用
write()将缓冲区中的数据写入目标文件。 - 循环执行,直到读取完所有数据。
- 使用
-
关闭文件:
- 使用
close()关闭文件描述符,释放系统资源。
- 使用
总结
open():打开文件,获取文件描述符。read():读取文件内容。write():写入文件内容。close():关闭文件描述符。
这些系统调用是 Linux 文件 I/O 的核心,通过它们可以实现高效的文件操作,同时也避免了高级文件操作接口(如 C 标准库 fopen())带来的额外开销。
二、Q:Linux编写一个程序,首先打开一个文件,然后利用 fork () 创建一个子进程;随后,当父进程运行时先执行 write ();父子进程都打印自己和其父进程的 ID 号;并且二者都向该文件写入 (利用 write ()) 一条信息,表明置在哪个进程中。试问:若没有 wait () 调用,会出现什么情况?
A:以下是完成上述功能的 C 程序,同时对程序行为进行了分析,特别是当没有 wait() 调用时会出现的情况。
程序代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main() {
// 打开文件,准备写入内容
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("Error opening file");
exit(1);
}
// 创建子进程
pid_t pid = fork();
if (pid < 0) {
perror("Fork failed");
close(fd);
exit(1);
}
if (pid == 0) {
// 子进程逻辑
char child_msg[] = "This is written by the child process.\n";
dprintf(fd, "Child process ID: %d, Parent process ID: %d\n", getpid(), getppid());
write(fd, child_msg, sizeof(child_msg) - 1);
printf("Child: PID = %d, Parent PID = %d\n", getpid(), getppid());
} else {
// 父进程逻辑
char parent_msg[] = "This is written by the parent process.\n";
dprintf(fd, "Parent process ID: %d, Child process ID: %d\n", getpid(), pid);
write(fd, parent_msg, sizeof(parent_msg) - 1);
printf("Parent: PID = %d, Child PID = %d\n", getpid(), pid);
// 如果没有 wait(),父进程将不会等待子进程结束
// wait(NULL); // 可取消注释,观察加入 wait() 后的效果
}
// 关闭文件
close(fd);
return 0;
}
程序解析
-
打开文件:
- 使用
open()以写模式打开文件output.txt,如果文件不存在则创建。 - 设置了
O_TRUNC标志,确保文件内容在程序启动时被清空。
- 使用
-
创建子进程:
- 使用
fork()创建子进程。 fork()的返回值:- 返回
0表示当前是子进程。 - 返回子进程的 PID 表示当前是父进程。
- 返回负值表示进程创建失败。
- 返回
- 使用
-
父进程和子进程的行为:
- 父进程:
- 打印自己的 PID 和子进程的 PID。
- 向文件写入父进程相关的信息。
- 子进程:
- 打印自己的 PID 和父进程的 PID。
- 向文件写入子进程相关的信息。
- 父进程:
-
没有
wait()的影响:- 如果没有
wait(),父进程不会等待子进程完成,而是立即继续执行(可能先于子进程结束)。 - 在这种情况下,父进程可能比子进程先完成,而子进程仍在运行,变成孤儿进程,由
init进程接管。 - 文件内容的写入顺序可能变得不可预测,因为父子进程是并发运行的。
- 如果没有
运行结果分析
文件内容
在没有 wait() 的情况下,文件 output.txt 的内容可能会出现以下几种情况:
正常写入(无竞争):
Parent process ID: 1234, Child process ID: 1235
This is written by the parent process.
Child process ID: 1235, Parent process ID: 1234
This is written by the child process.
竞争条件(进程调度引起的写入顺序变化):
Child process ID: 1235, Parent process ID: 1234
This is written by the child process.
Parent process ID: 1234, Child process ID: 1235
This is written by the parent process.
父进程完成,子进程仍在写入:
文件可能只包含子进程的内容,或者文件的写入内容不完整(具体取决于调度和缓冲行为)。
控制台输出
输出父子进程的 ID,顺序可能不确定:
Parent: PID = 1234, Child PID = 1235
Child: PID = 1235, Parent PID = 1234
或者:
Child: PID = 1235, Parent PID = 1234
Parent: PID = 1234, Child PID = 1235
若加入 wait() 的效果
在父进程中加入 wait() 后:
- 父进程会等待子进程结束,然后再继续执行。
- 文件内容的顺序将始终保持一致:
Parent process ID: 1234, Child process ID: 1235
This is written by the parent process.
Child process ID: 1235, Parent process ID: 1234
This is written by the child process.
控制台输出顺序也会固定为:
Parent: PID = 1234, Child PID = 1235
Child: PID = 1235, Parent PID = 1234
没有 wait() 的问题总结
-
不确定的执行顺序:
- 父子进程是并发执行的,因此文件内容的写入顺序可能不同,每次运行结果可能变化。
-
资源未释放:
- 如果父进程没有调用
wait(),子进程的退出状态不会被回收,子进程会变成 僵尸进程(Zombie Process)。
- 如果父进程没有调用
-
孤儿进程:
- 如果父进程比子进程先结束,子进程会成为孤儿进程,由
init进程(PID 为 1)接管。
- 如果父进程比子进程先结束,子进程会成为孤儿进程,由
解决建议
为了避免上述问题:
- 使用
wait()或waitpid()来确保父进程等待子进程完成。 - 如果父进程不需要等待子进程,可以用
SIGCHLD信号处理僵尸进程,或者直接使用daemon()函数生成孤儿进程。
三、解释下面一段代码
/* pipedemo.c——演示使用管道机制进行I/O控制 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, char **argv){
int p_fd[2];
pid_t pid;
char buf[1024];
if (pipe(p_fd) < 0) {
perror("pipe");
return -1;
}
if ((pid = fork()) < 0) {
perror("fork");
return -1;
}
else if (pid == 0) {
close(p_fd[1]);
read(p_fd[0], buf, 1024);
printf("Child read: %s", buf);
close(p_fd[0]);
return 0;
}
else {
close(p_fd[0]);
write(p_fd[1], "Hello, world!", 13);
close(p_fd[1]);
wait(NULL);
return 0;
}}
A:这段程序使用 管道(pipe) 机制在父进程和子进程之间传递数据。以下是对代码的详细解析和说明:
程序功能概述
- 使用
pipe()创建一个管道,形成父子进程之间的通信通道。 - 使用
fork()创建子进程。 - 父进程通过管道向子进程写入一段消息
"Hello, world!"。 - 子进程从管道中读取这段消息并打印到标准输出。
代码关键点分析
1. pipe()
管道是 UNIX 中的一种进程间通信机制,pipe() 创建一个匿名的管道,生成两个文件描述符(p_fd[0] 和 p_fd[1]):
p_fd[0]:管道的读取端。p_fd[1]:管道的写入端。
if (pipe(p_fd) < 0) {
perror("pipe");
return -1;
}
如果 pipe() 返回值小于 0,说明管道创建失败。
2. fork()
fork() 用于创建子进程,返回值有三种情况:
- 返回负值:创建子进程失败。
- 返回 0:当前是子进程。
- 返回子进程的 PID:当前是父进程。
3. 子进程逻辑
else if (pid == 0) {
close(p_fd[1]); // 关闭写入端(子进程只需要读取)
read(p_fd[0], buf, 1024); // 从管道读取数据到缓冲区 buf
printf("Child read: %s", buf); // 打印读取到的内容
close(p_fd[0]); // 关闭读取端
return 0; // 子进程退出
}
- 子进程关闭了管道的写入端(
p_fd[1]),确保它只使用管道的读取端。 - 使用
read()从管道读取数据,读取到的内容存储在buf中。 - 打印读取到的消息,并关闭读取端后退出。
4. 父进程逻辑
else {
close(p_fd[0]); // 关闭读取端(父进程只需要写入)
write(p_fd[1], "Hello, world!", 13); // 向管道写入数据
close(p_fd[1]); // 写入完成后关闭写入端
wait(NULL); // 等待子进程退出
return 0; // 父进程退出
}
- 父进程关闭了管道的读取端(
p_fd[0]),确保它只使用管道的写入端。 - 使用
write()向管道写入一段消息"Hello, world!"。 - 写入完成后关闭写入端,并调用
wait()等待子进程结束,避免子进程变成僵尸进程。
程序运行示例
假设程序文件名为 pipedemo.c,可以按如下方式编译并运行:
gcc -o pipedemo pipedemo.c
./pipedemo
程序输出
Child read: Hello, world!
程序中的关键概念
1. 管道(pipe)
- 管道是一种单向通信机制(默认情况下),数据只能从写入端流向读取端。
- 匿名管道的特性:
- 必须由亲缘关系的进程共享(如父子进程)。
- 写入端关闭后,读取端会返回 EOF。
2. 同步机制
- 在管道通信中,父进程和子进程的执行顺序是不确定的。
- 关键同步点:
- 父进程在写入数据后关闭写入端,这会让子进程的
read()在读取到数据后立即返回。 - 父进程调用
wait()等待子进程结束,避免出现子进程成为僵尸进程的情况。
- 父进程在写入数据后关闭写入端,这会让子进程的
如果移除 wait(NULL),会发生什么?
-
潜在问题:
- 如果父进程没有调用
wait(),它可能在子进程完成之前结束运行。 - 子进程会成为 僵尸进程(Zombie Process),因为父进程未收集子进程的退出状态。
- 僵尸进程不会消耗 CPU,但会占用系统资源(如 PID 表项)。
- 如果父进程没有调用
-
影响:
- 子进程的输出可能仍然会正常显示,但系统中会残留一个僵尸进程,直到父进程结束或父进程被
init接管。
- 子进程的输出可能仍然会正常显示,但系统中会残留一个僵尸进程,直到父进程结束或父进程被
改进建议
-
使用
wait():- 始终调用
wait(),确保父进程正确收集子进程的退出状态。
- 始终调用
-
更健壮的实现:
如果需要避免僵尸进程,也可以在父进程中使用SIGCHLD信号处理:signal(SIGCHLD, SIG_IGN);
这样,子进程退出时,系统会自动回收其资源。
总结
- 该程序演示了使用管道在父子进程间传递数据。
- 关键点在于正确关闭管道的读写端,避免资源泄漏或阻塞。
- 调用
wait()是良好实践,可以确保子进程正确退出,避免僵尸进程的问题。
四、解释代码
/* msg_q.c - 演示消息队列的写入和读出 */
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSZ 512
/* 消息结构 */
struct msg {
long type;
char text[BUFSZ];};
int main(void) {
int qid; /* 队列ID */
key_t key; /* 队列键值 */
int len1, len2;
struct msg pmsg_w, pmsg_r;
key = IPC_PRIVATE; /* 使用私有键值 */
/* 创建队列 */
if ((qid = msgget(key, IPC_CREAT | 0666)) < 0) {
perror("msgget: create");
exit(EXIT_FAILURE);
}
puts("Enter message to post:");
/* 获取消息 */
if (fgets(pmsg_w.text, BUFSZ, stdin) == NULL) {
puts("Wrong, no message to post.");
exit(EXIT_FAILURE);
}
pmsg_w.type = 10; /* 设置消息类型 */
len1 = strlen(pmsg_w.text); /* 计算消息长度 */
/* 把消息加到队列中 */
if (msgsnd(qid, &pmsg_w, len1, IPC_NOWAIT) < 0) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
puts("Message posted.");
puts("***************");
/* 从队列中提取并显示消息 */
len2 = msgrcv(qid, &pmsg_r, BUFSZ, 10, IPC_NOWAIT | MSG_NOERROR);
if (len2 > 0) {
pmsg_r.text[len2] = '\0'; /* 确保字符串以空字符结尾 */
printf("Reading queue id=%05d\n", qid);
printf("Message type=%05ld\n", pmsg_r.type);
printf("Message length=%d bytes\n", len2);
printf("Message text=%s\n", pmsg_r.text);
} else {
perror("msgrcv");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);}
A:这段代码演示了如何使用 System V 消息队列 在 Linux 系统中实现消息的发送和接收。以下是对代码的详细解析和说明:
代码功能概述
- 创建一个 私有的消息队列。
- 从标准输入读取一条消息。
- 将消息发送到消息队列中。
- 从消息队列中读取该消息并打印其内容。
代码关键点解析
1. 消息队列的基本概念
- 消息队列 是一种进程间通信(IPC)机制,允许进程以 消息为单位 进行异步通信。
- 关键系统调用:
msgget():创建或获取消息队列。msgsnd():向消息队列中发送消息。msgrcv():从消息队列中接收消息。msgctl():控制消息队列(例如删除队列)。
2. 消息结构
struct msg {
long type; // 消息类型(必须是大于 0 的正整数)
char text[BUFSZ]; // 消息正文
};
type是消息的类型,接收消息时可以指定接收特定类型的消息。text是实际传递的消息内容。
3. 创建消息队列:msgget()
if ((qid = msgget(key, IPC_CREAT | 0666)) < 0) {
perror("msgget: create");
exit(EXIT_FAILURE);
}
key = IPC_PRIVATE:表示创建一个私有的消息队列,只能被当前进程和其子进程访问。IPC_CREAT:如果队列不存在,则创建它。0666:设置消息队列的权限为可读写(所有用户)。
返回值:
- 成功:返回消息队列的 ID。
- 失败:返回
-1,通过perror()输出错误信息。
4. 发送消息:msgsnd()
if (msgsnd(qid, &pmsg_w, len1, IPC_NOWAIT) < 0) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
msgsnd()将消息添加到消息队列中。- 参数说明:
qid:消息队列 ID。&pmsg_w:要发送的消息。len1:消息正文的长度(不包括type)。IPC_NOWAIT:非阻塞模式,如果消息队列已满,立即返回错误。
- 可能的错误:
- 消息队列已满(
EAGAIN)。 - 其他权限或参数错误。
- 消息队列已满(
5. 接收消息:msgrcv()
len2 = msgrcv(qid, &pmsg_r, BUFSZ, 10, IPC_NOWAIT | MSG_NOERROR);
msgrcv()从消息队列中提取消息。- 参数说明:
qid:消息队列 ID。&pmsg_r:存储接收消息的结构体。BUFSZ:读取消息正文的最大长度。10:指定接收类型为10的消息。IPC_NOWAIT:非阻塞模式,如果队列为空,立即返回错误。MSG_NOERROR:如果消息正文长度超过BUFSZ,则截断消息,而不会报错。
- 返回值:
- 成功:返回接收到的消息正文的长度。
- 失败:返回
-1,通过perror()输出错误信息。
6. 消息队列使用的步骤
- 创建消息队列(
msgget())。 - 构造消息内容(
struct msg)。 - 使用
msgsnd()发送消息。 - 使用
msgrcv()接收消息。 - 程序退出后,队列会自动释放(本例未调用
msgctl()删除队列)。
程序运行示例
编译
gcc -o msg_q msg_q.c
运行
./msg_q
输入输出
- 程序提示输入一条消息:
Enter message to post: Hello from message queue! - 程序会将消息发送到队列并接收,输出如下内容:
Message posted. *************** Reading queue id=XXXXX Message type=00010 Message length=25 bytes Message text=Hello from message queue!
关键点与注意事项
1. IPC_PRIVATE
- 本例使用
IPC_PRIVATE作为消息队列的键值。 - 这会创建一个私有消息队列,只能被当前进程访问,且在程序退出后系统会自动删除。
2. 消息类型
type是消息的类型,接收消息时可以通过指定type提取特定类型的消息。- 如果
type为0,会提取队列中的第一个消息(忽略类型)。
3. 缓冲区管理
- 消息正文长度必须小于或等于
BUFSZ,否则会被截断(如使用MSG_NOERROR标志)。
4. 异常处理
- 如果
msgsnd()或msgrcv()返回错误,应检查:- 消息队列是否已满(发送时)。
- 消息队列是否为空(接收时)。
- 权限问题或消息长度问题。
程序改进建议
-
删除消息队列:
使用msgctl()删除队列,避免可能的资源泄露。msgctl(qid, IPC_RMID, NULL); -
可重用的键值:
使用ftok()创建可重用的键值,而不是IPC_PRIVATE,例如:key = ftok("filename", 65); -
多消息支持:
可以扩展程序支持发送和接收多条消息。
总结
该程序清晰演示了如何创建、发送和接收消息队列中的消息,适合作为学习 System V 消息队列 的入门示例。通过改进,可以将其扩展为多进程通信的实际应用场景。
五、Q:linux:编写一个程序,创建一个子进程;父进程向子进程发送一个信号,然后等待子进程终止;子进程接收信号,输出自己的状态信息,最后终止自己。
A:以下是一个完整的 Linux C 程序,它展示了如何创建一个子进程,父进程向子进程发送信号,子进程接收信号后输出状态信息并终止。
程序代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
// 子进程的信号处理函数
void signal_handler(int signo) {
if (signo == SIGUSR1) {
printf("Child: Received SIGUSR1 signal.\n");
printf("Child: PID = %d, PPID = %d\n", getpid(), getppid());
printf("Child: Terminating now.\n");
exit(0); // 子进程终止
}
}
int main() {
pid_t pid;
// 创建子进程
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
}
if (pid == 0) {
// 子进程
printf("Child: Started, waiting for signal...\n");
// 注册信号处理函数
signal(SIGUSR1, signal_handler);
// 无限等待信号
while (1) {
pause(); // 等待信号
}
} else {
// 父进程
printf("Parent: PID = %d, Child PID = %d\n", getpid(), pid);
sleep(1); // 确保子进程运行并等待信号
printf("Parent: Sending SIGUSR1 to Child (PID = %d)...\n", pid);
kill(pid, SIGUSR1); // 向子进程发送 SIGUSR1 信号
// 等待子进程结束
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("Parent: Child terminated with exit code %d.\n", WEXITSTATUS(status));
} else {
printf("Parent: Child terminated abnormally.\n");
}
printf("Parent: Exiting.\n");
}
return 0;
}
程序解析
1. 信号机制简介
信号是 Linux 系统中用于进程间通信的基本机制之一,可以在进程之间异步传递消息。程序中用到了以下信号相关功能:
signal():注册信号处理函数。kill():向指定进程发送信号。pause():使进程挂起,等待信号。
2. 子进程逻辑
- 子进程启动后,会调用
signal(SIGUSR1, signal_handler)注册信号处理函数。 - 子进程使用
pause()挂起自己,等待接收信号。 - 收到
SIGUSR1信号后,信号处理函数被调用,打印子进程的状态信息,然后调用exit(0)正常退出。
3. 父进程逻辑
- 父进程通过
fork()创建子进程,并获取子进程的 PID。 - 使用
kill()向子进程发送SIGUSR1信号。 - 调用
waitpid()等待子进程退出,并检查其退出状态。
4. 输出说明
运行程序
假设程序名为 signal_demo.c,编译并运行:
gcc -o signal_demo signal_demo.c
./signal_demo
输出示例
Child: Started, waiting for signal...
Parent: PID = 1234, Child PID = 1235
Parent: Sending SIGUSR1 to Child (PID = 1235)...
Child: Received SIGUSR1 signal.
Child: PID = 1235, PPID = 1234
Child: Terminating now.
Parent: Child terminated with exit code 0.
Parent: Exiting.
程序关键点
1. 信号处理函数
子进程使用 signal() 注册了一个信号处理函数 signal_handler,当收到 SIGUSR1 信号时会被调用。
void signal_handler(int signo) {
if (signo == SIGUSR1) {
printf("Child: Received SIGUSR1 signal.\n");
printf("Child: PID = %d, PPID = %d\n", getpid(), getppid());
printf("Child: Terminating now.\n");
exit(0);
}
}
信号处理函数中可以访问信号的标识符(如 SIGUSR1),并执行具体的逻辑。
2. 信号发送
父进程使用 kill(pid, SIGUSR1) 向子进程发送信号。
pid是目标进程的 PID。SIGUSR1是一个用户自定义信号。
3. 父进程等待子进程
父进程使用 waitpid() 等待子进程结束,并检查退出状态:
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("Parent: Child terminated with exit code %d.\n", WEXITSTATUS(status));
}
WIFEXITED(status):判断子进程是否正常退出。WEXITSTATUS(status):获取子进程的退出码。
程序改进建议
-
信号可靠性:
signal()在部分实现中可能不够可靠,可以用sigaction()替代来确保更强的信号处理能力:struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGUSR1, &sa, NULL); -
处理多个信号:可以扩展程序,让子进程支持接收多个不同的信号。
-
添加错误处理:在
fork()、kill()等系统调用后,添加详细的错误处理代码以提高鲁棒性。
总结
- 该程序通过信号实现父子进程间的简单通信。
- 父进程负责向子进程发送信号,子进程接收信号并完成自己的逻辑。
- 程序展示了
fork()、kill()、waitpid()和signal()的基本用法,非常适合作为信号通信的入门示例。

浙公网安备 33010602011771号