Linux进程间共享信号量的实现 (linux不同进程信号灯共享)

在Linux系统中,进程间的通信是非常常见的场景。其中,信号量是一种常用的进程间通信机制,它可以用来实现进程的同步和互斥。在多进程或多线程的环境中,控制进程之间的访问同步和互斥是非常重要的,而信号量正是解决这一问题的有效机制。

本文将介绍Linux中进程间共享信号量的实现方法,包括信号量的定义、初始化、操作等,希望能够帮助读者深入理解信号量的知识,更好地应用它们进行进程间通信。

一、信号量的定义

信号量是一种用于同步进程和线程的机制,它主要用于控制进程的互斥和同步。在Linux系统中,信号量是由一个整形变量和一组信号量操作组成的。信号量变量的值表示当前资源的可用数目。当信号量的值为正数时,表示还有可用资源;当信号量的值为0时,表示所有资源都被占用;当信号量的值为负数时,表示有进程正在等待释放资源。

在Linux系统中,信号量的定义如下:

“`

struct sembuf{

unsigned short sem_num; // 信号量编号

short sem_op; // 信号量操作

short sem_; // 操作标志

};

union semun{

int val; // 信号量初始值

struct semid_ds *buf; // IPC_STAT、IPC_SET 操作用缓存区

unsigned short *array; // SETALL、GETALL 操作用数组

};

int semget(key_t key, int nsems, int sem); // 创建或打开一个信号量

int semctl(int semid, int semnum, int cmd, union semun arg); // 对一个信号量或信号量执行控制操作

int semop(int semid, struct sembuf *sops, unsign int nsops); // 执行信号量操作

“`

二、信号量的初始化

在使用信号量进行进程间通信时,需要先创建或打开一个信号量,并进行初始化。在Linux中,信号量的初始化可以使用 semget() 函数和 semctl() 函数进行。

1.使用 semget() 函数创建信号量

(1)semget() 函数原型

“`

#include

#include

#include

int semget(key_t key, int nsems, int sem);

“`

(2)参数说明

key:用于标识信号集的key值。

nsems:创建的信号集的数量。

sem:信号集的访问权限。

(3)函数返回值

成功:返回信号量集的标识符。

失败:返回 -1 并设置 errno 来指示错误的类型。

(4)示例代码

“`

#include

#include

#include

#include

int mn()

{

int semid;

key_t key = 1234;

semid = semget(key, 1, 0666 | IPC_CREAT);

if (semid == -1) {

perror(“semget”);

return -1;

}

printf(“succeed to create semaphore: %d\n”, semid);

return 0;

}

“`

上面的示例代码使用 key 为 1234 创建了一个信号量,该包含了一个信号量。sem 的值被设置为 0666 | IPC_CREAT 来表示新创建的信号量,如果已经存在该信号量,则直接打开该信号量。

2. 使用 semctl() 函数初始化信号量

使用 semctl() 函数初始化信号量时,可以设置信号量的值、操作等。semctl() 函数使用如下:

(1)semctl() 函数原型

“`

#include

#include

#include

int semctl(int semid, int semnum, int cmd, union semun arg);

“`

(2)参数说明

semid:信号量集的标识符。

semnum:对信号量中的第几个信号量进行操作。

cmd:操作类型,包括如下:

– SETVAL:设置信号量集中的一个信号量的值。

– IPC_RMID:从内核中删除信号量。

arg:参数类型 union semun,可以是如下情况:

– val:val 成员用于 SETVAL 操作设置一个信号量或数组的值。

– buf:buf 成员用于 IPC_STAT 和 IPC_SET 操作,获取或修改信号量的状态信息。

– array:array 成员用于 SETALL 和 GETALL 操作,设置或获取整个信号量数组的值。

(3)函数返回值

成功:返回信号量集的标识符。

失败:返回 -1 并设置 errno 来指示错误的类型。

(4)示例代码

“`

#include

#include

#include

#include

int mn()

{

int semid;

key_t key = 1234;

union semun arg;

struct sembuf sembuf;

int val = 1;

semid = semget(key, 1, 0666 | IPC_CREAT);

if (semid == -1) {

perror(“semget”);

return -1;

}

arg.val = val;

if (semctl(semid, 0, SETVAL, arg) == -1) {

perror(“semctl”);

return -1;

}

printf(“succeed to initialize semaphore: %d\n”, semid);

return 0;

}

“`

上面的示例代码创建一个 key 值为 1234 的信号量,包含了一个信号量。使用 SETVAL 操作设置该信号量的值为 1。如果初始化成功,则返回该信号量的标识符 semid。

三、信号量的操作

在信号量的中,可以对单个信号量进行操作,也可以对信号量中的所有信号量进行操作。信号量的操作主要包括加操作和减操作。在 Linux 平台下,可以使用 semop() 函数完成对信号量的操作。

在使用 semop() 函数对信号量进行操作时,需要传入一个结构体 sembuf 作为信号量操作的参数,或者传入多个 sembuf,以便一次执行多个信号量操作。

(1)sembuf 结构体的定义

“`

struct sembuf {

unsigned short sem_num; // 信号量中的信号量编号

short sem_op; // 信号量操作

short sem_; // 操作标志

};

“`

sem_num:信号量中的信号量编号。

sem_op:信号量的操作,其中,信号量的操作可以为如下值之一:

– 正整数:表示向信号量中增加信号量值;

– 负整数:表示从信号量中减去信号量值;

– 0:表示读取信号量中的值。

sem_:操作标志,可用于 IPC_NOWT 等标志的设置。

(2)semop() 函数原型

“`

#include

#include

#include

int semop(int semid, struct sembuf *sops, unsigned nsops);

“`

(3)参数说明

semid:信号量的标识符。

sops:指向 sembuf 结构数组的指针,其中每一个 sembuf 结构体描述了一个信号量的操作。

nsops:sops 指向的 sembuf 结构体的数量。

(4)函数返回值

成功:返回 0。

失败:返回 -1 并设置 errno 来指示错误的类型,如 EAGN 表示操作被阻塞,EINVAL 不能操作等等。

(5)信号量操作的示例代码

下面的示例代码展示了如何在两个进程中对共享的信号量进行操作。两个进程的实现方式分别是获取和释放共享资源。

之一个进程:

“`

#include

#include

#include

#include

#include

#include

const char *data = “hello, world”;

int p(int semid, int semnum)

{

struct sembuf sem_buf;

sem_buf.sem_num = semnum;

sem_buf.sem_op = -1; // 清空信号量

sem_buf.sem_ = SEM_UNDO; // 系统内核在进程退出时恢复信号量

return semop(semid, &sem_buf, 1);

}

int v(int semid, int semnum)

{

struct sembuf sem_buf;;

sem_buf.sem_num = semnum;

sem_buf.sem_op = 1; // 加 1

sem_buf.sem_ = SEM_UNDO; // 系统内核在进程退出时恢复信号量

return semop(semid, &sem_buf, 1);

}

int mn()

{

int semid;

key_t key = 1234;

pid_t pid;

union semun arg;

struct sembuf sembuf;

int val = 1;

semid = semget(key, 1, 0666 | IPC_CREAT);

if (semid == -1) {

perror(“semget”);

return -1;

}

arg.val = val;

if (semctl(semid, 0, SETVAL, arg) == -1) {

perror(“semctl”);

return -1;

}

pid = fork();

if (pid == -1) {

perror(“fork”);

return -1;

} else if (pid == 0) {

// 子进程

p(semid, 0);

printf(“child process get the semaphore, write data\n”);

write(STDOUT_FILENO, data, strlen(data));

sleep(1);

v(semid, 0);

printf(“child process release the semaphore\n”);

} else {

// 父进程

struct sembuf sem_buf;

sem_buf.sem_num = 0;

sem_buf.sem_op = -1; // 清空信号量

sem_buf.sem_ = SEM_UNDO; // 系统内核在进程退出时恢复信号量

semop(semid, &sem_buf, 1);

printf(“parent process get the semaphore, read data\n”);

read(STDIN_FILENO, data, strlen(data));

v(semid, 0);

printf(“parent process release the semaphore\n”);

}

semctl(semid, 0, IPC_RMID, arg);

return 0;

}

“`

上面的代码中,fork() 函数创建一个子进程,在子进程中利用信号量实现获取和释放共享资源的操作。在父进程中也是利用信号量控制读取和释放共享资源的操作。

四、

相关问题拓展阅读:

linux进程间通讯的几种方式

一。管道(pipe)

  管道是Linux支持的最初IPC方式,管道可分为无名管道,有名管道等。

  (一)无名管道,它具有几个特点:

  1) 管道是半双工的,只能支持数据的单向流动;两进程间需要通信时需要建立起两个管道;

  2) 无名管道使用pipe()函数创建,只能用于父子进程或者兄弟进程之间;

  3) 管道对于通信的两端进程而言,实质上是一种独立的文件,只存在于内存中;

  4) 数据的读写操作:一个进程向管道中写数据,所写的数据添加在管道缓冲区的尾部;另一个进程在管道中缓冲区的头部读数据。

  (二)有名管道

  有名管道也是半双工的,不过它允许没有亲缘关系的进程间进行通信。具体点说就是,有名管道提供了一个路径名与之进行关联,以FIFO(先进先出)的形式存在于文件系统中。这样即使是不相干的进程也可以通过FIFO相互通信,只要他们能访问已经提供的路径。

  值得注意的是,只有在管道有读端时,往管道中写数据才有意义。否则,向管道写数据的进程会接收到内核发出来的SIGPIPE信号;应用程序可以自定义该信号处理函数,或者直接忽略该信号。

  二。信号量(semophore)

  信号量是一种计数器,可以控制进程间多个线程或者多个进程对资源的同步访问,它常实现为一种锁机制。实质上,信号量是一个被保护的变量,并且只能通过初始化和两个标准的原子操作(P/V)来访问。(P,V操作也常称为wait(s),signal(s))

  三。信号(Signal)

  信号是Unix系统中使用的最古老的进程间通信的方法之一。操作系统通过信号来通知某一进程发生了某一种预定好的事件;接收到信号的进程可以选择不同的方式处理该信号,一是可以采用默认处理机制—进程中断或退出,一是忽略该信号,还有就是自定义该信号的处理函数,执行相应的动作。

  内核为进程生产信号,来响应不同的事件,这些事件就是信号源。信号源可以是:异常,其他进程,终端的中断(Ctrl-C,Ctrl+\等),作业的控制(前台,后台进程的管理等),分配额问题(cpu超时或文件过大等),内核通知(例如I/O就绪等),报警(计时器)。

  四。消息队列(Message Queue)

  消息队列就是消息的一个链表,它允许一个或者多个进程向它写消息,一个或多个进程向它读消息。Linux维护了一个消息队列向量表:msgque,来表示系统中所有的消息队列。

  消息队列克服了信号传递信息少,管道只能支持无格式字节流和缓冲区受限的缺点。

  五。共享内存(shared memory)

  共享内存映射为一段可以被其他进程访问的内存。该共享内存由一个进程所创建,然后其他进程可以挂载到该共享内存中。共享内存是最快的IPC机制,但由于linux本身不能实现对其同步控制,需要用户程序进行并发访问控制,因此它一般结合了其他通信机制实现了进程间的通信,例如信号量。

  socket也是一种进程间的通信机制,不过它与其他通信方式主要的区别是:它可以实现不同主机间的进程通信。

进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,一般,对于单个程序而言使用所有的IPC方法是不常见的。

1、无名管道通信

无名管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用,进程的亲缘关系通常是指父子进程关系。

2、高级管道通信

高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们称为高级管道方式。

3、有名管道通信

有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

4、消息队列通信

消息队列(message

queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识,消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5、信号量通信

信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问,它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

6、信号

信号(sinal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

7、共享内存通信

共享内存(shared

memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

8、套接字通信

套接字(socket):套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

LINUX下系统编程,多进程间数据共享,即共享变量

There are programming details at this site:

Unix多进程编程和进程间的通讯

有详细的源程序,我就不一一复制。

linux 可以照猫画虎。

共享内存相关的API怎么使用不难:

1. 首先调用shmget分配一个新的共享内存,这里你可以指定其大小,如果你要分配一个整形,那你可以将size参数设置成4,如果你要共享一个结构体那就将size参数设置成你的结构体大小,操作系统不关心你要共享什么,它只关心你要分配多少个字节的区间。而且实际上操作系统会将你要求的大小按照内存页面的大小进行对齐,也就是说它可能实际上给你分配若干个页面的物理存储空间,只要这个空间能够容纳你所指定的大小就ok了。它的第三个参数是关于一些访问权限设置的,要讲起来太长,建议自己搜索一下,或者用man查查帮助。总之,调用完shmget以后系统会给你创建一段共享内存,然后返回给你一个shmid,也就是这个共享内存的标识,你可以理解为给它取了个名字。

2. 接着调用shmat将这段共享内存映射到你的进程的虚拟地址空间上。这个函数的之一个参数就是你之前调用shmget创建的共享内存的名字shmid;第二个参数是个指针,指向你的进程虚存空间中的某个地址,你可以通过传入一个确定的地址强行要求操作系统将共享内存映射到你指定的虚存地址上(可能会失败,如果你指定的虚拟地址空间已经映射了别的物理存储空间),也可以通过传入0地址让系统给你选择一个合适的地址(它会通过返回值把地址返回给你)。第三个参数则允许你指定一些特殊的标志位,还是那句话,太复杂自己搜索一下看看,一般应用不需要用到。

至于例子嘛你可以看看下面这个链接:

另外,你要知道只用共享内存是不互斥的,你必须结合信号量一起使用才能防止互斥问题的出现。如果你共享的只是一个整形变量可能问题不大,因为对页面对齐的整形变量的读写都是原子操作,但如果你共享的是个复杂的结构体就得小心了。

简单的实现,没有添加同步机制,回头再添加上去,而且,我是在不同终端里面写的,你可以把两段代码,一个放到父进程,一个放到子进程…就可以了

你说的这些API,自己man 一次,看看说明就知道用法了….

楼上说的对齐的问题,我没有太注意..不过,不管你要共享什么,一个sizeof看看大小,一个memcpy拷贝,你就作为数据直接拷贝到共享内存区域就OK了…另外一边再拷贝回来,用一个结构体类型的指针指向你拷贝回来的数据,不就给这部分内存再规划成一个结构体了。。

至于具体的, KEY 的含义,你需要了解linux的ipc机制。

#include

#include

#include

#include

#define BUF_SIZE 100

#define KEY 99

int main(void)

{

int shmid;

char *shmptr;

shmid=shmget(99,BUF_SIZE,IPC_CREAT|0666);

if(shmid==-1)

{

printf(“Shared Memory Created error…\n”);exit(0);

}

shmptr=shmat(shmid,NULL,0);

if(shmptr==(void*)-1)

{

printf(“shmat error,shmptr= %d \n”,shmptr);

exit(1);

}

while(1)

{

printf(“type strings into Shared Memory:”);

fgets(shmptr,BUF_SIZE,stdin);

}

return 0;

}

下面这段就每隔10秒钟扫描共享内存区域的内容:

#include

#include

#include

#include

#define BUF_SIZE 100

#define KEY 99

int main(void)

{

int shmid;

char *shmptr;

shmid=shmget(99,BUF_SIZE,IPC_CREAT|0666);

if(shmid==-1)

{

printf(“Shared Memory Created error…\n”);exit(0);

}

shmptr=shmat(shmid,NULL,0);

if(shmptr==(void*)-1)

{

printf(“shmat error,shmptr= %d \n”,shmptr);

exit(1);

}

while(1)

{

printf(“Infomation in Shared Memory:”);

printf(“%s \n”,shmptr);

sleep(10);

}

return 0;

}

帮你顶,希望有这方面专业知道的朋友能站出来帮助你解决困难.

关于linux不同进程信号灯共享的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。


数据运维技术 » Linux进程间共享信号量的实现 (linux不同进程信号灯共享)