Linux进程通信(信号量、共享内存)

Linux进程通信(信号量、共享内存)

一、信号量(Semaphore)

1.1 简介

几个概念:

原文链接:https://blog.csdn.net/dove1202ly/article/details/79999540

进程互斥:由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程竞争使用这些资源,进程的这种关 系为进程的互斥。也就是说一个资源每次只能被一个进程访问。

进程同步:在访问资源的时候,以某种特定顺序的方式区访问资源,也就是多个进程需要相互配合共同完成一项资 源。

临界资源(互斥资源):系统中某些资源一次只允许一个进程使用。

临界区:在进程中涉及到互斥资源的程序段。

  1. Linux下的信号量是什么?

    信号量是一种特殊的变量,用来保护共享资源(临界资源)。当一个进程访问这个共享资源时,不能给其他进程使用这个资源,否则会出现奇怪的问题。

  2. 如何使用信号量保护共享资源?

    进程在使用共享资源之前先请求信号量,请求得到信号量就可以使用共享资源,否则不能使用,在使用完之后释放掉信号量。例如,对于二值信号量,不可能有两个进程同时访问某个共享资源,因为二值信号量只有0和1两个值,一个进程申请了信号量,另一个进程只能等待前一个进程使用完毕再去申请。

1.2 相关函数

semget创建
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

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

参数说明:

key与之前的消息队列中的key类似,同样可以使用ftok函数获取。

nsems:即为num of sems,信号量的数量,如果是二值信号量设置为1

semflg:标志,如果semflg为key指定了IPC_CREAT和IPC_EXCL,并且已经存在一个信号量集,则semget()失败, errno设置为EEXIST。(这类似于O_CREAT | O_EXCL for open的效果(2))。

返回值:如果成功,返回值将是信号量集标识符(非负整数),否则返回-1,errno表示错误。

semop操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

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

参数:

semid:信号量的标识id。

sops:这个参数是一个sembuf结构体,包含以下成员:

struct sembuf{  
    short sem_num;//除非使用一组信号量,否则它为0  
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,  
                    //一个是+1,即V(发送信号)操作。  
    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,  
                   //并在进程没有释放该信号量而终止时,操作系统释放信号量  
};	

nsops:由sops指向的数组中的每个nsops元素都是一个结构,该结构指定要在单个信号量上执行的操作。

返回值:成功返回0,失败返回-1,置相应的errno

semctl控制
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);

参数:

semnum:信号量编号,说明是对哪个信号量进行的操作

cmd:操作命令,选择SETVAL设置初始化信号量,IPC_RMID为删除信号量。

第四个参数一般是一个共用体:

union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
};

一般只使用val,传递信号量的初始值。

返回值:失败返回-1,当cmd设置不同的值时会返回不同的内容,但都是非负值。

1.3 同步实验

示例代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.h>

//semctl函数需要使用的共用体类型
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

int sem_id;
static int set_semvalue()
{
    union semun sem_union;    
    sem_union.val = 1;//使用成员val给信号量传值
    if(semctl(sem_id,0,SETVAL,sem_union)==-1)//设置失败
        return 0;
    return 1;
}
static int semaphore_p()
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;//除非使用一组信号量,否则它为0  
    sem_b.sem_op = -1;//-1为减,即请求信号
    sem_b.sem_flg = SEM_UNDO;//使操作系统跟踪信号
    if(semop(sem_id,&sem_b,1)==-1)
    {
        fprintf(stderr,"semaphore_p failed\n");
        return 0;
    }
    return 1;
}
static int semaphore_v()
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;//+1发送信号
    sem_b.sem_flg = SEM_UNDO;
    if(semop(sem_id,&sem_b,1)==-1)
    {
        fprintf(stderr,"semaphore_v failed\n");
        return 0;
    }
    return 1;
}
static void del_semvalue()
{
    //删除信号量
    union semun sem_union;
    if(semctl(sem_id,0,IPC_RMID,sem_union)==-1)//删除失败
        fprintf(stderr,"Failed to delete semaphore\n");
}
/*主函数*/
int main(int argc,char *argv[])
{
    char message = 'x';
    //创建信号量
     sem_id = semget((key_t)1234,1,0666|IPC_CREAT);
    if(argc>1)
    {//如果参数大于1
        //初始化信号量
        if(!set_semvalue())
        {
            fprintf(stderr,"init failed\n");
            exit(EXIT_FAILURE);
        }
        //参数1的第一个字符赋给message
        message = argv[1][0];
    }
    int i=0;
    for(i=0;i<5;i++)
    {
        //等待信号量
        if(!semaphore_p())
            exit(EXIT_FAILURE);
        printf("%c",message);
        fflush(stdout);
        sleep(1);
        //发送信号量
        if(!semaphore_v())
            exit(EXIT_FAILURE);
        sleep(1);
    }
    printf("\n%d-finished\n",getpid());
    if(argc>1)
    {
        //退出前删除信号量
        del_semvalue();
    }
    exit(EXIT_SUCCESS);
}
执行结果
在这里插入图片描述
分析

使用./sem2 0 & ./sem2命令运行了两个程序,第一个程序因为传入了两个参数(argc>2)会进行信号量的初始化和删除,message字符也被设置为传入的字符0,第二个程序只有一个参数,因此message值为x。可以利用信号量来进行进程同步,使进程按照一定的先后顺序去执行。

二、共享内存

2.1 简介

共享内存进行进程通信的思想非常简单。多个进程共同享有一段内存,某个进程想内存中写数据,其他共享这段内存的进程就可以通过读的方式去获取数据,从而实现通信。

共享内存基本操作步骤:创建、映射、脱离。

2.2 步骤

下面介绍一下这三个方法使用的API函数。

shmget创建
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

参数说明:

key:与之前的很多API使用方式相同,可以用ftok获取。

size:顾名思义就是创建的内存的大小

shmflg:标志,仍然与前面的方法类似,可以使用IPC_CREAT创建,加上IPC_EXCL创建的话如果存在就会出错返 回,详见man手册。

返回值:成功返回共享内存的标识shmid,失败返回-1并置相应errno。

shmat附加

at即为attch附加的含义,shmat()将shmid标识的System V共享内存段连接到调用进程的地址空间。

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数说明:

shmid:共享内存标识id。

shmaddr:看名字就知道是地址,是附加到进程地址空间的共享内存地址,满足以下情况:

​ 如果shmaddr为空(NULL),系统将选择一个合适的(未使用的)地址来附加这个段。

​ 如果shmaddr不为空,SHM_RND在shmflg中指定,则附加发生在等于shmaddr的地址处,该地址四 舍五入为SHMLBA的最近倍数。

​ 其他情况,shmaddr必须是与页对齐的地址,在该地址处发生附加。

shmflg:参考上一个函数。

返回值:成功返回一个指针,指向附加的共享内存段的地址;错误返回-1,同样会置errno相应的错误。

shmdt脱离

dt意思是detach分离、脱离。shmdt()将位于shmaddr指定地址的共享内存段从调用进程的地址空间中分离出来。

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);

参数说明:

shmaddr:需要脱离的共享内存地址。

返回值:成功返回0,失败返回-1并置errno。

shmctl操作

这个函数根据传入的参数对共享内存进行操作,可以对共享内存进行初始化或者删除创建的共享内存,当我们不再使用共享内存时就可以对其进行删除操作。

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数说明:

shmid:共享内存标识id。

cmd:操作命令。

buf:这是shmid_ds结构体指针,结构体类型为:

struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };

返回值:失败返回-1并置相应的errno,成功根据传入的命令不同有不同的返回值。

2.3 共享内存读写实验

代码

shmWrite.c:

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>  
#include <sys/shm.h>  
#include "shmdata.h"  
  
int main(void)  
{  
    int running = 1;  
    void *shm = NULL;  
    struct shared_use_st *shared = NULL;  
    char buffer[BUFSIZ + 1];//用于保存输入的文本  
    int shmid;  
    //创建共享内存  
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);  
    if(shmid == -1)  
    {  
        fprintf(stderr, "shmget failed\n");  
        exit(EXIT_FAILURE);  
    }  
    //将共享内存连接到当前进程的地址空间  
    shm = shmat(shmid, (void*)0, 0);  
    if(shm == (void*)-1)  
    {  
        fprintf(stderr, "shmat failed\n");  
        exit(EXIT_FAILURE);  
    }  
    printf("Memory attached at %p\n", shm);  
    //设置共享内存  
    shared = (struct shared_use_st*)shm;  
    while(running)//向共享内存中写数据  
    {  
        //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本  
        while(shared->written == 1)  
        {  
            sleep(1);  
            printf("Waiting...\n");  
        }  
        //向共享内存中写入数据  
        printf("Enter some text: ");  
        fgets(buffer, BUFSIZ, stdin);  
        strncpy(shared->text, buffer, TEXT_SZ);  
        //写完数据,设置written使共享内存段可读  
        shared->written = 1;  
        //输入了end,退出循环(程序)  
        if(strncmp(buffer, "end", 3) == 0)  
            running = 0;  
    }  
    //把共享内存从当前进程中分离  
    if(shmdt(shm) == -1)  
    {  
        fprintf(stderr, "shmdt failed\n");  
        exit(EXIT_FAILURE);  
    }  
    sleep(2);  
    exit(EXIT_SUCCESS);  
}

shmRead.c:

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <sys/shm.h>  
#include <string.h>
#include "shmdata.h"  
  
int main(void)  
{  
    int running = 1;//程序是否继续运行的标志  
    void *shm = NULL;//分配的共享内存的原始首地址  
    struct shared_use_st *shared;//指向shm  
    int shmid;//共享内存标识符  
    //创建共享内存  
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);  
    if(shmid == -1)  
    {  
        fprintf(stderr, "shmget failed\n");  
        exit(EXIT_FAILURE);  
    }  
    //将共享内存连接到当前进程的地址空间  
    shm = shmat(shmid, 0, 0);  
    if(shm == (void*)-1)  
    {  
        fprintf(stderr, "shmat failed\n");  
        exit(EXIT_FAILURE);  
    }  
    printf("\nMemory attached at %p\n", shm);  
    //设置共享内存  
    shared = (struct shared_use_st*)shm;//强制转换为shared_use_st指针
    shared->written = 0;  
    while(running)//读取共享内存中的数据  
    {  
        //没有进程向共享内存定数据有数据可读取  
        if(shared->written != 0)  
        {  
            printf("You wrote: %s", shared->text);  
            sleep(rand() % 3);  
            //读取完数据,设置written使共享内存段可写  
            shared->written = 0;  
            //输入了end,退出循环(程序)  
            if(strncmp(shared->text, "end", 3) == 0)  
                running = 0;  
        }  
        else//有其他进程在写数据,不能读取数据  
            sleep(1);  
    }  
    //把共享内存从当前进程中分离  
    if(shmdt(shm) == -1)  
    {  
        fprintf(stderr, "shmdt failed\n");  
        exit(EXIT_FAILURE);  
    }  
    //删除共享内存  
    if(shmctl(shmid, IPC_RMID, 0) == -1)  
    {  
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");  
        exit(EXIT_FAILURE);  
    }  
    exit(EXIT_SUCCESS);  
}

shmdata.h:

#ifndef _SHMDATA_H_HEADER  
#define _SHMDATA_H_HEADER  
  
#define TEXT_SZ 2048  
  
struct shared_use_st  
{  
    //作为一个标志,非0:表示可读,0表示可写
	int written;
	//记录写入和读取的文本	
    char text[TEXT_SZ];  
};  
  
#endif
运行测试
在这里插入图片描述
分析

首先在后台运行R读程序,然后在运行W写程序,在通过写端写入键盘输入的数据到共享内存中,然后读端读出内存中的text数据,输入end可以在读写两端跳出循环,执行脱离、删除操作,最后退出。

2.4 多进程操作共享内存

源码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h> 
 
int main(int argc,char **argv)
{
	int shmid;
	char *p_addr,*c_addr;
	if(argc!=2)
	{
		fprintf(stderr,"Usage:%s + str\n\a",argv[0]);
		exit(1);
	}
	if((shmid=shmget((key_t)1234, 1024, 0666|IPC_CREAT))==-1)
	{
		fprintf(stderr,"Create Share Memory Error: %s\n\a",strerror(errno));
		exit(1);
	}
	//创建子进程
	if(fork())//父进程写
	{
		p_addr=shmat(shmid,0,0);
		memset(p_addr,'\0',1024);
		strncpy(p_addr,argv[1],1024);
        printf("father process send: %s\n", p_addr);
		wait(NULL);  //释放资源,不关心终止情况
        printf("wait sonProc finish!\n");
        if(shmdt(p_addr) < 0)
        {
            perror("father proc shmdt");
            exit(1);
        }
		if(shmctl(shmid, IPC_RMID, 0) < 0)//删除共享内存
			perror("shmctl");
		exit(0);
	}
	else	//子进程读
	{
		sleep(1);
		c_addr=shmat(shmid,0,0);
		printf("son process get: %s\n",c_addr);
        if(shmdt(c_addr) < 0)
        {
            perror("son proc shmdt");
            exit(1);
        }
		exit(0);
	}
}
运行结果
在这里插入图片描述
分析

使用fork创建一个子进程,在父进程中接入共享内存并写数据到共享内存,接着使用wait(NULL);等待子进程运行结束。运行结束后在父进程中删除共享内存。最后可以使用ipcs -m命令查看内存是否删除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值