进程间的通信方式(管道,消息队列,共享内存,信号)

了解

创建进程后实现父子通讯的连接。
我们希望有一个管道来进行数据的交互。之前可以用exit和exec族函数来假通信。数据很有限。
所以我们使用IPC,进程的通信。
在这里插入图片描述

使用管道来通信

无名管道

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

https://blog.csdn.net/baidu_38621657/article/details/105724822>

怎么理解半双工呢:父写数据在管道,子进程读。在同一时间,父子只能一个读,一个写。
且!:管道中的数据读走之后就没有了。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
        int fd[2];
        int pid;
        char buf[128];

        if(pipe(fd) == -1)
        {
                printf("创建管道失败!\n");
        }
        pid = fork();

        if(pid < 0)
        {
                printf("进程创建失败!\n");
        }

        else if(pid > 0)
        {
                printf("我是董瑞龙的父亲\n");
                close(fd[0]);
                //ssize_t write(int fd, const void *buf, size_t count);
                write(fd[1],"Ha Ha ha!\n",strlen("Ha Ha ha!"));
                wait();
        }

        else
        {
                printf("董瑞龙我儿!\n");
                close(fd[1]);
                //ssize_t read(int fd, void *buf, size_t count);
                read(fd[0],buf,128);
                printf("read data is %s\n",buf);
                exit(0);
        }

        return 0;
}

有名管道

FIFO、、使用mkfifo

与无名管道原理一样在这里插入图片描述

当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  • 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
  • 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 读取消息:成功返回消息数据的长度,失败返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息队列:成功返回0,失败返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

这里要记住一个api:msgctl函数。作用是关闭队列。RMID:从内核中移除。
在这里插入图片描述在这里插入图片描述

key 键值生成

ftok,去内核找到相关id 第一个是路径名,第二个是…随意
ls-ai id值通过十六进制计算。

各种通信方式的不同: 管道就像一个通道,信息就像上课传的小纸条(有去无回),比如你给女神表白她一直不回你。
消息队列就像一个箱子,可以放进去可以看,看完再把东西放进去。这种可以双方通信,比较优势。 共享内存是什么呢?
我可以给我的女神同桌在桌子上表白,女神可以马上看到。在内存中有一块公共内存,我和女神都可以看到。

共享内存

1:创建共享内存。
2:映射。(干掉)

1、shm函数(创建共享内存)
函数声明:
int shmget(key_t ey,size_t size, int shmflg);
第一个参数key:为共享内存段命名,有一个特殊的键值IPC_PRIVATE,用于创建一个只属于创建进程的共享内存
第二个参数size:是共享内存的大小(字节数)
第三个参数shmflg:包含9个比特的权限标志,作用同文件操作是的mode标志位相同
返回值:函数执行成功返回一个非负整数,即内存标识符,失败时返回-1.
2、shmat函数(将共享内存连接到进程)
   第一次创建共享内存段,该共享内存段并不能被任何进程所使用,只有当共享内存被连接到一个进程,才可以访问共享内存,这个功能由shmat函数实现。
函数声明:
void *shmat(int shm_id, const oid *shm_addr, int shmflg);
第一个参数:所要连接的共享内存标识符。
第二个参数:指定共享内存连接到当前进程的地址位置,一般传NULL,表示由系统选择共享内存连接的位置。
第三个参数:可取SHM_RND和SHM_RDONLY(使得连接的内存地址只读)
返回值:函数执行成功返回共享内存的首地址,失败返回-1
3、shmdt函数(分离共享内存)

该函数的参数是shmat函数返回的共享内存地址指针,成功返回0,失败返回-1.
4、shmctl(控制函数)
函数声明:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数:共享内存标识符
第二个参数://要采取的动作,有如下取值:
             IPC_STAT:把shm_ds结构中的数据设置为共享内存的当前关联值
             IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构体中的值
             IPC_RMID:删除共享内存。
第三个参数:buf是一个指针,指向包含共享内存模式和访问权限的结构
返回值:成功返回0失败返回-1.
struct shmid_ds
{
    uid_t shm_perm.uid;   
    uid_t shm_perm.gid;    
    mode_t shm_perm.mode;    
}

https://blog.csdn.net/qq_41727218/article/details/82772056

shmat使用时后面俩个变量写0,一个是让Linux自动分配共享内存,另一个是权限。

信号

类似于51的定时器,一种信号来中断你现在处理的事情,信号也有优先级。在Linux实际上是软中断,51是硬中断。

信号概述

1.信号的名字和编号:

每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。

2.信号的处理:

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

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是
    SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。

其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps
命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是
9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。在这里插入图片描述

下面我们可以自己实现对Linux命令ctrl c的改写并通过软件编程实现对进程的kill。

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

void change(int num)
{
        printf("get num is = %d\n",num);
        printf("quit is useless!");
}

int main()
{
        signal(SIGINT,change);
        while(1);
        return 0;
}
这是阻止Linux   shell命令ctrl c 的代码

那么我们如何自己去解锁呢?

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>

int main(int argc , int **argv)
{
        int num;
        int pid;

        num = atoi(argv[1]);
        pid = atoi(argv[2]);

        printf("argc is %d\n",argc);
        printf("num=%d,pid=%d\n",num,pid);

        kill(pid,num);
        printf("welldone!\n");

        return 0;
}
~     这是删除命令

在这里插入图片描述
那么完成之后,删除命令也是可以优化的:使用system函数
在这里插入图片描述

信号如何携带消息

入门版:函数signal
高级版:函数sigaction
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只能任选其一
 siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
               pid_t    si_pid;      /* Sending process ID */
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
               sigval_t si_value;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
}

信号发送函数:

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

下面是通过信号来实现消息的发送与接受:

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

void doo(int num,siginfo_t *info,void *context)
{
        printf("get signum = %d\n",num);

        if(context != NULL)
        {
                printf("get data = %d\n",info->si_int);
                printf("get data = %d\n",info->si_value.sival_int);//俩种数据获取方式
                printf("data from %d\n",info->si_pid);
        }
}

int main()
{
        struct sigaction act;
        printf("get pid = %d\n",getpid());

        act.sa_sigaction = doo;
        act.sa_flags = SA_SIGINFO;//可以接受到消息

        sigaction(SIGUSR1,&act,NULL);
        while(1);
        return 0;
}
这是收
#include <stdio.h>
#include <signal.h>

int main(int argc ,char **argv)
{
        int num;
        int pid;

        num = atoi(argv[1]);
        pid = atoi(argv[2]);

        union sigval value;
        value.sival_int = 520;

        sigqueue(pid,num,value);
        printf("getpid = %d\n",getpid());

        return 0;
}
这是发,可以更改为字符串消息

在这里插入图片描述

信号量

信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
简单的说就是管理,比如控制共享内存一个读一个写,不同时进行。
信号量集。分为p操作和v操作。

1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
5 int semop(int semid, struct sembuf semoparray[], size_t numops);  
6 // 控制信号量的相关信息
7 int semctl(int semid, int sem_num, int cmd, ...);

1 struct sembuf 
2 {
3     short sem_num; // 信号量组中对应的序号,0~sem_nums-1
4     short sem_op;  // 信号量值在一次操作中的改变量
5     short sem_flg; // IPC_NOWAIT, SEM_UNDO
6 }

semget函数:
第二个参数是信号量集信号量的个数,第三个跟共享内存一样,可以使用IPC_CREAT|0600,
semctl函数:
第一个是操作量级id,第二代表要操作第几个信号量,第三个参数可以使用SETVAL来设置初值,根据man 要求定义联合体

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) */
};

// P操作:
 //    若信号量值为1,获取资源并将信号量值-1 
//    若信号量值为0,进程挂起等待
 int sem_p(int sem_id)
{
     struct sembuf sbuf;
      sbuf.sem_num = 0; /*序号*/信号量编号
      sbuf.sem_op = -1; /*P操作*/操作无法进入
     sbuf.sem_flg = SEM_UNDO;//等待
 
     if(semop(sem_id, &sbuf, 1) == -1)
     {
          perror("P operation Error");
         return -1;
      }
      return 0;
  }
  // V操作:
  //    释放资源并将信号量值+1
  //    如果有进程正在挂起等待,则唤醒它们
  int sem_v(int sem_id)
  {
     struct sembuf sbuf;
      sbuf.sem_num = 0; /*序号*/
      sbuf.sem_op = 1;  /*V操作*/
      sbuf.sem_flg = SEM_UNDO;
 
     if(semop(sem_id, &sbuf, 1) == -1)
     {
          perror("V operation Error");
         return -1;
      }
      return 0;
  }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

llechee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值