这个代码片段展示了一个父子进程之间使用信号量来进行互斥访问文件的例子。让我们逐行分析这个代码的工作原理和可能存在的问题。
代码分析
引入头文件:
#include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h> #include <string.h> #include "sem_pv.h"
unistd.h
、sys/stat.h
、fcntl.h
等头文件用于提供POSIX API。"sem_pv.h"
应该是你自己实现的信号量操作相关的头文件,包含了sem_init
,sem_p
,sem_v
, 和sem_rmv
的声明。
主函数入口:
int main(void) { key_t key; int pid, fd, semid, n; char str[80]; key = ftok(".", 5); semid = sem_init(key); fd = open("test.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
- 使用
ftok
生成一个唯一的键值,用于创建信号量。 - 创建/打开一个名为
test.txt
的文件以供读写。如果文件不存在,则创建它。
- 使用
创建子进程:
while ((pid = fork()) == -1); /* Loop until fork is successful */ if (pid == 0) /* Child process */ { sleep(1); sem_p(semid); lseek(fd, SEEK_SET, 0); read(fd, str, sizeof(str)); sem_v(semid); printf("child: read str from test file: %s\n", str); exit(0); } else /* Parent process */ { sem_p(semid); printf("parent: please enter a str for test file (strlen<80):\n"); gets(str); // Warning: unsafe function n = strlen(str); lseek(fd, SEEK_SET, 0); write(fd, str, n); sem_v(semid); wait(0); // Wait for child to finish close(fd); // Close the file descriptor sem_rmv(semid); // Remove semaphore exit(0); }
功能说明:
- 父进程先请求信号量,然后等待用户输入字符串并写入到文件中。
- 子进程在稍微延迟后请求信号量,读取同一文件中的内容,并打印出来。
存在的问题及改进建议
使用不安全的
gets()
函数:gets()
是一个不安全的函数,因为它不会检查缓冲区溢出。应替换为更安全的输入方式,如fgets()
。
fgets(str, sizeof(str), stdin); // Safe alternative to gets()
信号量初始化和错误处理:
- 在调用
sem_init(key)
和其他系统调用后,应该对返回值进行错误检查,以确保程序在出现错误时能够适当处理。
- 在调用
没有考虑并发访问问题:
- 如果多个子进程同时尝试读取或写入同一个文件,可能会产生竞争条件,因此需要确保每个进程都能正确地获取和释放信号量。
避免魔法数字:
- 对于像“5”和“80”这样的魔法数字,可以用常量或宏定义来代替,使得代码更加可读。
资源管理:
- 确保在所有情况下(包括异常退出)都能正确关闭打开的文件描述符,以及移除信号量。
完整示例(带改进)
下面是带有上述建议的小改动版本:
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include "sem_pv.h"
#define MAX_STR_LEN 80
int main(void)
{
key_t key;
int pid, fd, semid;
char str[MAX_STR_LEN];
key = ftok(".", 5);
if ((semid = sem_init(key)) == -1) {
perror("Failed to initialize semaphore");
exit(EXIT_FAILURE);
}
if ((fd = open("test.txt", O_RDWR | O_CREAT | O_TRUNC, 0644)) == -1) {
perror("Failed to open file");
exit(EXIT_FAILURE);
}
if ((pid = fork()) == -1) {
perror("Fork failed");
exit(EXIT_FAILURE);
}
if (pid == 0) { // Child process
sleep(1);
sem_p(semid);
lseek(fd, SEEK_SET, 0);
read(fd, str, sizeof(str));
sem_v(semid);
printf("child: read str from test file: %s\n", str);
exit(EXIT_SUCCESS);
} else { // Parent process
// Ensure proper use of fgets instead of gets
sem_p(semid);
printf("parent: please enter a string for test file (max length %d):\n", MAX_STR_LEN-1);
fgets(str, sizeof(str), stdin);
int n = strlen(str);
lseek(fd, SEEK_SET ,0 );
write(fd,str,n);
// Release semaphore before waiting on child process.
// Otherwise we will have a deadlock situation.
sem_v(semid);
wait(NULL); // Wait for child to finish
close(fd); // Close the file descriptor
if (sem_rmv(semid) == -1) {
perror("Failed to remove semaphore");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);}
}
通过这些改动,你可以提高程序的安全性、健壮性以及可读性。
内容由零声教学AI助手提供,问题来源于学员提问