Linux多进程编程(7):共享资源的信号量处理,保障数据安全
在 Linux 多进程编程的相关章节里,也就是章节(7),我们对共享进程资源的重要性进行了深入的探讨。这种探讨虽然显著提升了系统的执行效率,然而也引发了一些挑战。当多个进程同时进行读写共享内存的操作时,数据安全性方面的问题就显现出来了。正是为了能够解决这些出现的问题,我们有必要引入信号量这一概念。

1.什么是信号量
信号量是在多进程环境中被使用的一种方法。它可用于实现进程间的互斥与同步,这样就能保证进程能够正确且合理地使用公共资源。信号量是一个计数器,能用来控制多个进程对共享资源的访问。常用的信号量函数有:sem_open(),它用于打开或创建一个信号量;sem_close(),用于关闭一个信号量;sem_wait(),用于等待信号量;sem_post(),用于发送信号量;sem_unlink(),用于删除一个信号量。接下来将对这些函数逐一进行介绍。
2.sem_open()
sem_open()的作用是打开或创建一个命名信号量,它的定义情况如下:
sem_t * 用于打开一个已存在的信号量或者创建一个新的信号量。通过 const char *name 参数指定信号量的名称。使用 int oflag 参数来指定打开或创建信号量的标志。后面还可以跟其他参数(用...表示)来进一步控制信号量的操作。
其中,name 指代信号量的名字;oflag 是打开标志,常见的有 O_CREAT 和 O_EXCL 。O_CREAT 的意思是若名字的信号量不存在就创建它,而 O_EXCL 是在创建新信号量时使用的,要是名字的信号量已经存在就会返回错误。……意味着当使用 O_CREAT 标志时,是用于指定信号量初始值的参数。如果没有使用 O_CREAT,则此参数被忽略。
函数执行成功时,会返回一个指向信号量的指针;而在失败的情况下,会返回 SEM_FAILED。
3.sem_close()
sem_close()用于关闭已打开的信号量,其定义如下:
int sem_close(sem_t *sem);
其中,sem为指向已打开的信号量的指针。
若函数执行成功,返回 0,失败返回-1。
4.sem_wait()
sem_wait()用于对信号量进行等待操作,它会让调用进程处于等待状态,直至信号量的值变为大于零。其具体定义如下:
int sem_wait(sem_t *sem);
其中,sem为指向信号量的指针。
若函数执行成功,返回 0,失败返回-1。
5.sem_post()
sem_post()的作用是,当一个进程完成了对共享资源的访问后,就会释放信号量,如此一来,其他正在等待该信号量的进程就能够继续执行。其定义情况如下:
int sem_post(sem_t *sem);
其中,sem为指向信号量的指针。
若函数执行成功,返回 0,失败返回-1。
6.sem_unlink()
sem_unlink()的作用是在一个命名信号量不再被需要时,把它从系统里移除掉。它的定义情况如下:
int 函数用于删除一个已存在的命名信号量。该函数接受一个指向以空字符结尾的字符串的指针作为参数,这个字符串指定了要删除的信号量的名称。通过调用 int sem_unlink(const char *name) 函数,可以从系统中删除指定名称的信号量。
其中,name为信号量的名字。
若函数执行成功,返回 0,失败返回-1。
7.举例
目的是让父进程和子进程能够操作共享内存,子进程承担写入数据“你好,新年快乐”的任务,父进程则负责读出共享数据“你好,新年快乐”。同时,通过信号量来进行同步,以此保证在任何时刻都只有一个进程能够对共享内存进行访问。
#include // 用于标准输入输出
#include // 用于常用库函数,如内存分配
#include // 用于字符串操作函数
#include // 用于底层系统调用,如fork()
#include // 用于内存映射和信号量等系统调用
#include // 用于等待子进程
#include // 用于信号量操作
#include
#include
#include
#include
int main(){
int shm; // 共享内存标识符
key_t key; // 用于创建共享内存的键值
pid_t pid; // 进程ID
创建或打开一个名为“/my_semaphore”的信号量,其权限为 0666,初始值为 1,创建的信号量用 sem 指针指向,sem 为 sem_t 类型指针,即 sem = sem_open("/my_semaphore", O_CREAT, 0666, 1);
char * buff; // 指向共享内存的指针
使用文件系统中的唯一键值来创建键,这个键用于创建共享内存对象,其中 key 等于 ftok(".", 'R');
shm 获取共享内存标识符,并且在需要的情况下创建共享内存,具体是通过 shmget 函数,传入 key 作为参数,指定大小为 1024,以及权限设置为 0666 或 IPC_CREAT 。
buff 等于通过 shmat 函数将共享内存附加并获取其地址,这里的共享内存参数为 shm,且偏移量为 NULL,标志位为 0。
pid=fork(); // 创建子进程
if(pid<0){ // 如果fork()失败
perror("创建进程失败"); // 打印错误信息
}
else if(pid==0){ // 子进程代码块
printf("这是子进程\n");
sem_wait(sem); // 子进程等待信号量,确保父进程先执行
printf("子进程锁住资源\n");
strcpy(buff,"你好,新年快乐"); // 向共享内存写入数据
printf("%s\n",buff); // 打印共享内存中的数据
sem_post(sem); // 子进程释放信号量,允许其他进程继续执行
printf("子进程释放资源\n");
}
else{ // 父进程代码块
printf("这是父进程\n");
sleep(1); // 父进程等待1秒,确保子进程先执行
sem_wait(sem); // 父进程等待信号量,确保子进程已经执行完毕
printf("父进程锁住资源\n");
printf("%s\n",buff); // 打印共享内存中的数据
sem_post(sem); // 父进程释放信号量,允许其他进程继续执行
printf("父进程释放资源\n");
}
sem_close(sem); // 关闭信号量
sem_unlink("/my_semaphore"); // 删除信号量对象
shmdt(buff); // 断开共享内存的连接,使其可以被系统回收
shmctl(shm,IPC_RMID,NULL); // 删除共享内存对象
}
运行程序,得到结果: