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


参考链接

链接:https://www.jianshu.com/p/f445bfeea40a

一、共享内存

共享内存,顾名思义,就是两个或多个进程都可以访问的同一块内存空间,一个进程对这块空间内容的修改可为其他参与通信的进程所看到的。

显然,为了达到这个目的,就需要做两件事:一件是在内存划出一块区域来作为共享区;另一件是把这个区域映射到参与通信的各个进程空间。

原型

获取创建共享内存

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

挂载映射到进程上

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

如果一个进程已创建或打开一个共享内存,则在需要使用它时,要调用函数shmat()把该共享内存连接到进程上,即要把待使用的共享内存映射到进程空间。函数shmat()通过系统调用sys_shmat()实现。

释放连接

int shmdt(const void *shmaddr);

删除共享内存

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

代码示例

实现两个进程的通信

#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//       int shmget(key_t key, size_t size, int shmflg);
int main()
{
        char *shmaddr;
        key_t key;
        key=ftok(".",1);  //key还是用ftok函数生成比较专业一点
        int shmid;
        shmid=shmget(key,1024*4,IPC_CREAT|0600);  //创建共享内存时,大小是以M为基本的 最小也得1M
        if(shmid==-1){
                printf("shmget failed!\n");
                exit(-1);
        }
        shmaddr=shmat(shmid,0,0);  //映射 连接进程
        printf("shmat ok!\n");
        strcpy(shmaddr,"jia you ao li gei!!!");   //往映射区写入
        sleep(5); //给5秒时间读取
        shmdt(shmaddr);      //释放映射
        shmctl(shmid,IPC_RMID,0); //删除共享区域
        printf("quit \n");
        return 0;
}

#include <sys/ipc.h>
#include <sys/shm.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//       int shmget(key_t key, size_t size, int shmflg);
int main()
{
        char *shmaddr;
        key_t key;
        key=ftok(".",1);
        int shmid;
        shmid=shmget(key,1024*4,0);  //因为是读 不需要创建  就写0 
        if(shmid==-1){
                printf("shmget failed!\n");
                exit(-1);
        }
        shmaddr=shmat(shmid,0,0);

        printf("shmat ok!\n");
        printf("read from w:%s\n",shmaddr);    //直接将映射区的内容打印出来  实现读

        shmdt(shmaddr);
        printf("quit \n");
        return 0;
}

在这里插入图片描述
在这里插入图片描述

查看共享内存的指令
ipcs -m
指令删除共享内存
ipcrm -m + shmid

二、信号

什么是信号

对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

指令 kill -l 查看信号名字及序号
在这里插入图片描述

处理方法

信号的处理有三种方法,分别是:忽略、捕捉和默认动作

忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景

捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。

系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。

捕捉信号里handler(信号处理函数)分两个版本

1.入门版:函数signal
2.高级版:函数sigaction

信号处理发送函数

1.入门版 kill
2.高级版 sigqueue

入门版

signal 的函数原型

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

根据函数原型可以看出由两部分组成,一个是真实处理信号的函数,另一个是注册函数了。
对于sighandler_t signal(int signum, sighandler_t handler);函数来说,signum 显然是信号的编号,handler 是中断函数的指针。
同样,typedef void (*sighandler_t)(int);中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。

编写入门版信号处理函数

#include <signal.h>
#include<stdio.h>
//       typedef void (*sighandler_t)(int);

  //     sighandler_t signal(int signum, sighandler_t handler);

void handler(int signum)
{
        printf("get signum=%d\n",signum);
        switch(signum){
                case 2:
                        printf("SIGINT\n");
                        break;
                case 9:
                        printf("SIGKILL\n");
                        break;
                case 10:
                        printf("SIGUSER1\n");
                        break;
        }

        printf("never quit!\n");
}

int main()
{
        signal(SIGINT,handler); //捕捉crtl c 然后执行handler函数 
        signal(SIGKILL,handler);
        signal(SIGUSR1,handler);
        while(1);
        return 0;
}

入门版发送函数

原型

int kill(pid_t pid, int sig);
#include <signal.h>
#include<stdio.h>
#include <sys/types.h>



int main(int argc,char **argv)  //这里用以前的老方法 argc v 收集指令
{
        int signum;
        int pid;
        char cmd[128]={0};  
        signum=atoi(argv[1]); //需注意 argv是个字符串 而指令输入的是整型  需要atoi函数进行转换
        pid=atoi(argv[2]);

        printf("signum=%d,pid=%d\n",signum,pid);
//      kill(pid,signum);  第一种方法  调用kill函数

        sprintf(cmd,"kill -%d %d",signum,pid);
        system(cmd);   //第二种调用system 但需要sprintf配合

        printf("send signal ok!\n");

        return 0;
}

SIG_IGN宏可以将信号忽略

高级版

我们已经成功完成了信号的收发,那么为什么会有高级版出现呢?其实之前的信号存在一个问题就是,虽然发送和接收到了信号,可是总感觉少些什么,既然都已经把信号发送过去了,为何不能再携带一些数据呢
正是如此,我们需要另外的函数来通过信号传递的过程中,携带一些数据。咱么先来看看发送的函数吧。

sigaction 的函数原型

include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一

sigaction 是一个系统调用,根据这个函数原型,我们不难看出,在函数原型中,第一个参数signum应该就是注册的信号的编号;第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。

处理函数

#include <signal.h>
#include <stdio.h>
       //int sigaction(int signum, const struct sigaction *act,
          //            struct sigaction *oldact);
void handler(int signum, siginfo_t *info, void *context)
{			//第一个参数 信号的kill 数值  第二个为指针 存放传递数据的值
		    //第三个 如果为空 说明没有内容
        printf("the signum is:%d\n",signum);
        if(context !=NULL){
                printf("get data=%d\n",info->si_int);
                printf("get data=%d\n",info->si_value.sival_int);
        }
}

int main()
{
        struct sigaction act;
        act.sa_sigaction=handler;   //处理函数
        act.sa_flags=SA_SIGINFO;   //表示能够接受数据
        printf("pid =%d\n",getpid());

        sigaction(SIGUSR1,&act,NULL);  //第一个参数为 处理的信号
        //第二个为sigaction结构体 ,备份 不需要就NULL
        while(1);
        return 0;
}
~   

在这里插入图片描述

发送

#include <signal.h>
#include <stdio.h>

//       int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char **argv)
{
        int signum;
        int pid;
        signum=atoi(argv[1]);
        pid=atoi(argv[2]);

        union sigval value;   //联合体 负责存放传送数据
        value.sival_int=100;

        sigqueue(pid,signum,value);   //进程号  信号值   传递内容联合体
        printf("done!\n");
        return 0;
}

发送字符串只能在 共享内存或者 同一进程下 才可以发送

三、信号量

信号量(semaphore)与已经介绍过的IPC结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

1、特点

1.信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
2.信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作。
3.每次对信号量的PV操作不仅限于对信号量加1或减1,而且可以加减任一正整数。
4.支持信号量组。

2、原型

最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二值信号量。而可以取多个正整数的信号量被称为通用信号量。

Linux下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
//创建或获取一个信号量组,若成功返回信号量集ID,失败返回-1

int semop(int semid, struct sembuf *sops, unsigned nsops);
//对信号量组进行操作,改变信号量的值;成功返回0,失败返回-1

int semctl(int semid, int semnum, int cmd, ...);
//控制信号量的相关信息

3、代码示例

  #include <sys/types.h>
  #include <sys/ipc.h>
  #include <sys/sem.h>
  #include <stdio.h>
  //int semget(key_t key, int nsems, int semflg);
  //int semctl(int semid, int semnum, int cmd, ...);
  //int semop(int semid, struct sembuf *sops, unsigned nsops);
  union semun {
                 int              val;    /* Value for SETVAL */
                 struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
                 unsigned short  *array;  /* Array for GETALL, SETALL */
                 struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                             (Linux-specific) */
  };
  
  
void pgk(int id) //接受信号量id参数
{
        struct sembuf sops;  
        sops.sem_num=0;  //信号量的编号
        sops.sem_op=-1; //拿走key 数量-1
        sops.sem_flg=SEM_UNDO; //一共两个flg UNDO NOWAIT 用NOWAIT就没有意义了
        semop(id,&sops,1); //第三个参数表示第二个参数的个数 因为信号集合1——多 
        printf("get key\n");
}

void vbk(int id)
{
        struct sembuf sops;
        sops.sem_num=0;
        sops.sem_op=1;
        sops.sem_flg=SEM_UNDO;
        semop(id,&sops,1);
        printf("put back key\n");
}

int main()
{
        key_t key;
        key=ftok(".",2);
        int semid;
        union semun initsem;
        initsem.val=0;  //信号量数量

        semid=semget(key,1,IPC_CREAT|0666);//创建/获取信号量
				//第二个参数 信号集里有1个信号量
        semctl(semid,0,SETVAL,initsem);//初始化信号量
				//第二个参数 初始化第1个信号量 第三四个参数设置信号量的值	
        int pid=fork();

        if(pid>0){
                pgk(semid);//先拿key 但是由于信号量初始化的时候为0 没有key只能阻塞至有key再运行
                printf("this is father!\n");
                vbk(semid);
        }
        else if(pid==0){
                printf("this is child\n");
                vbk(semid);
                //先返回key 如果父子进程都是pv操作 则无法控制先后
        }
        else{
                printf("fork failed\n");
        }
        return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值