linux系统编程之进程间的通信

23 篇文章 0 订阅
1. 进程通信的概念

在同一台pc机上的两个进程通信是单机通信,不同的pc机上的两个进程通信叫多机通信。IPC(interprocess communication)也就是进程通信。
简单的来说就是两个进程之间能够互通有无。可以参考博文: https://blog.csdn.net/lyn_00/article/details/84789508

进程间通信使用的工具:
在这里插入图片描述
精彩博文推荐: https://blog.csdn.net/wh_sjc/article/details/70283843

2. 管道

管道(一般指无名管道)是半双工的,数据只能单向流通有固定的读端和写端,并且只能用于父子进程或者兄弟进程,它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。要关闭管道只需将这两个文件描述符关闭即可。

在这里插入图片描述
传一个fd数组进去,会创建两个文件描述符,fd[0]是读端,fd[1]是写端。管道存在于内核

当读的时候发现管道里没有数据的时候就会阻塞,有数据的时候才会停止阻塞。

3. FIFO

FIFO称为有名管道。是一个特殊的文件存在于磁盘中,打开的方式有两种,阻塞和非阻塞,默认打开是以阻塞的方式打开,此时当一个进程以只读的形式打开FIFO,就会阻塞,等待某个进程以只写的形式打开FIFO。
FIFO存在于磁盘,并且不局限于有亲缘关系的进程,毫无关系的两个进程和可以通过FIFO进行通信,只是管道是半双工。

使用 mkfifo(filepath,mode)——filepath是文件路径,mode同creat的mode相同。

代码范例:

// read
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main() {
    int fd;
    char *buf = (char *)malloc(128);
    if (mkfifo("fifo1", 0600) == -1) {
        perror("mkfifo error");
        return -1;
    }

    fd = open("fifo1", O_RDONLY);
    if (fd == -1) {
        perror("open error");
        return -1;
    }

    while (1) {
        sleep(1);
        read(fd, buf, 128);
        printf("%s\n", buf);
    }

    return 0;
}
// write
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
    int fd;
    int i;
    fd = open("fifo1", O_WRONLY);
    if (fd == -1) {
        printf("open error\n");
        return -1;
    }
    while (1) {
        write(fd, "hello world", 12);
        sleep(1);
    }
    return 0;
}


4. 消息队列

消息队列是面向记录的,消息队列位于内核,是一个链表,内核中有多个消息队列并且有单独的队列号来标识他们。
消息队列存在于内核,进程读消息并不会使消息队列中的消息消除,并且可以读队列的第一个和按类型来读取消息。

消息队列API:

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);
10 // 产生key值,成功返回key值,失败返回-1
11 key_t ftok(const char *pathname, int proj_id);

在以下两种情况下,msgget将创建一个新的消息队列:

如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
key参数为IPC_PRIVATE。
函数msgrcv在读取消息队列时,type参数有下面几种情况:

type == 0,返回队列中的第一个消息;
type > 0,返回队列中消息类型为 type 的第一个消息;
type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。(其他的参数解释,请自行Google之)

代码范例:使用消息队列读写消息

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

struct Msg {
    long m_type;
    char m_text[128];
};

int main() {
    key_t key = ftok(".",1);
    int msgid;
    int cnt = 0;
    struct Msg message;
    msgid = msgget(key, IPC_CREAT | 0777);
    if (msgid == -1) {
        perror("msgget error");
        exit(1);
    }
    pid_t pid = fork();
    if (pid > 0) {
        while (1) {
            sleep(1);
            message.m_type = 666;
            strcpy(message.m_text, "message form father");
            msgsnd(msgid, &message, sizeof(struct Msg), 0);
        
            msgrcv(msgid,&message,sizeof(message),777,0);
            printf("father recive msg:%s\n", message.m_text);

            cnt++;
            if(cnt == 5){
                break;
            }
        }
    } else if (pid == 0) {
        int n_rcv = 0;
        while (1) {
            sleep(1);
            n_rcv = msgrcv(msgid, &message, sizeof(message), 666, 0);
            printf("child recive msg:%s,n_rcv:%d\n", message.m_text, n_rcv);
        
            message.m_type = 777;
            strcpy(message.m_text, "message form child");
            msgsnd(msgid, &message, sizeof(struct Msg), 0);
            if(cnt == 5){
                break;
            }
            cnt++;
        }

    }

    msgctl(msgid,IPC_RMID,NULL);
    return 0;
}

5. 共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
共享内存在内存当中开辟了一块公共的内存,A和B进程都能访问到这个内存,并且像操作普通内存一样操作这个内存区域。

1、特点
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

因为多个进程可以同时操作,所以需要进行同步。

信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

2.原型

1 #include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr); 
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
10 // 产生key值,成功返回key值,失败返回-1
11 key_t ftok(const char *pathname, int proj_id);

步骤 :

  1. 开辟共享内存——shmget
  2. 共享内存映射在进程——shmat
  3. 读写数据
  4. 断开连接——shmdt
  5. 删除共享内存——shmctl

因为使用的是同一块内存,所以不支持原子操作,即多个进程同时写就会覆盖掉数据,所以要规范,当某个进程写的时候,其他进程等待写的进程写完之后再读或者写。

代码范例:使用共享内存发送数据:

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


int main(){
    key_t key;
    int shmid;
    char *shmaddr;
    key=ftok(".",1);
    if(key == -1){
        perror("ftok error");
        return -1;
    }
    
    shmid = shmget(key,1024 * 4,IPC_CREAT|0777);
    if(shmid == -1){
         perror("shmget error");
        return -1;
    }

    shmaddr=shmat(shmid,0,0);
    
    strcpy(shmaddr,"this message from write");

    sleep(5);

    if(shmdt(shmaddr) == -1){
       perror("shmdt error");
       return -1;
    }

    shmctl(shmid,IPC_RMID,NULL);

    return 0;
}

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


int main(){
    key_t key;
    int shmid;
    char *shmaddr;
    char buf[1024];
    key=ftok(".",1);
    if(key == -1){
        perror("ftok error");
        return -1;
    }
    
    shmid = shmget(key,1024 * 4,IPC_CREAT|0777);
    if(shmid == -1){
         perror("shmget error");
        return -1;
    }

    shmaddr=shmat(shmid,0,0);
    
    strcpy(buf,shmaddr);
    printf("%s\n",buf);

    if(shmdt(shmaddr) == -1){
       perror("shmdt error");
       return -1;
    }

    shmctl(shmid,IPC_RMID,NULL);

    return 0;
}
5. 信号

精彩博文: https://blog.csdn.net/zb1593496558/article/details/80280346

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

#include <signal.h>
//  信号处理函数
typedef void (*sighandler_t)(int);
// 注册信号处理函数
sighandler_t signal(int signum, sighandler_t handler);

例:

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

//typedef void (*sighandler_t)(int);
void handler(int signum)
{
    if(signum == SIGIO)
        printf("SIGIO   signal: %d\n", signum);
    else if(signum == SIGUSR1)
        printf("SIGUSR1   signal: %d\n", signum);
    else
        printf("error\n");
}

int main(void)
{
    //sighandler_t signal(int signum, sighandler_t handler);
    signal(SIGIO, handler);
    signal(SIGUSR1, handler);
    printf("%d  %d\n", SIGIO, SIGUSR1);
    for(;;)
    {
        sleep(10000);
    }
    return 0;
}
//  该代码为进程注册了两次信号处理函数,当进程收到SIGIO,SIGUSR1信号的时候都会调用handler函数而不是系统调用,处理函数改为SIG_IGN宏可以忽略该信号,但是有些信号无法忽略。
#include <sys/types.h>
#include <signal.h>
// 发送信号
int kill(pid_t pid, int sig);

以上两种(signal,kill)无法使信号携带消息,而(sigsction,sigqueue)可以携带消息。

原型:

#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只能任选其一

int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

sigaction:不需要备份原来信号的配置就把oldact配置成NULL,并且在配置act结构体的时候,sa_flags要配置成SA_SIGINFO才能接收数据。并且要配置结构体的sa_sigaction为处理函数。该处理函数的第一个参数为信号编号,第二个参数为携带信息的结构体,存放着许多信息。第三个参数为一个指针,该指针用于判断有无数据,为空则无数据。
sigqueue:使用的是共用体,所以只能发送一种类型的数据,否则会段错误。

代码范例,使用信号传递整形值:

// signal_sigqueue.c
  1 #include <signal.h>
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 int main(int argc,char **argv){
  6     pid_t pid;
  7     int signum;
  8     union sigval value;
  9     value.sival_int = 100;
 10     signum = atoi(argv[1]);
 11     pid = atoi(argv[2]);
 12 
 13     sigqueue(pid,signum,value);
 14     printf("send signal success\n");
 15 
 16 }

// signal_sigaction.c
  1 #include <stdio.h>
  2 #include <signal.h>
  3 
  4 void handle(int signum, siginfo_t *info,void *context){
  5 ....
  6     printf("signum is %d\n",signum);
  7     if(context!=NULL){
  8         printf("value is %d\n",info->si_value.sival_int);
  9     }
 10 
 11 }
 12 
 13 int main(){
 14     struct sigaction act;
 15     act.sa_flags = SA_SIGINFO;
 16     act.sa_sigaction = handle;
 17     printf("pid is %d\n",getpid());
 18 
 19     sigaction(SIGINT,&act,NULL);
 20     while(1);
 21 
 22     return 0;
 23 }

6. 信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。可以理解成房间的钥匙,一个进程拿走了钥匙,也就是P操作,其他进程就进不了房间了,要把钥匙放回去,其他进程才可以进到房间。

1、特点
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。P(拿锁),V(释放锁)

每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

支持信号量组。

2、原型
最简单的信号量是只能取 0(不可进入) 和 1(可进入) 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

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

1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
第一个参数是键,第二个参数是信号量集中要有多少个信号量,第三个参数同creat的mode(IPC_CREAT|0777)
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1,第一个参数是信号量集的id,第二个参数是一个数组,第三个参数是代表第二个参数的数组有多少个成员
5 int semop(int semid, struct sembuf semoparray[], size_t numops);  
6 // 控制信号量的相关信息,第一个参数是信号量集中的id,第二个参数是信号量集中的第几个信号量,第三个参数是相关操作,请参考man手册或其他,第四个参数要传一个共用体
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 }

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

范例:共享内存和信号量同时使用:

//  shmandsem_boy.c
  1 #include <stdio.h>
  2 #include <sys/ipc.h>
  3 #include <sys/shm.h>
  4 #include <sys/types.h>
  5 #include <sys/sem.h>
  6 #include <string.h>
  7     
  8 union semun {
  9     int val;               /* Value for SETVAL */
 10     struct semid_ds *buf;  /* Buffer for IPC_STAT, IPC_SET */
 11     unsigned short *array; /* Array for GETALL, SETALL */
 12     struct seminfo *__buf; /* Buffer for IPC_INFO
 13                                                              (Linux-specific) */
 14 };
 15 //P操作   拿到锁   
 16 void Psem(int semid){
 17     struct sembuf set;
 18     set.sem_num = 0;
 19     set.sem_op = -1;
 20     set.sem_flg = SEM_UNDO;
 21     semop(semid,&set,1);
 22 }
 23  // V操作,释放锁
 24 void Vsem(int semid){
 25     struct sembuf set;
 26     set.sem_num = 0;
 27     set.sem_op = 1;
 28     set.sem_flg = SEM_UNDO;
 29     semop(semid,&set,1);
 30 }   
 31     
 32 int main() {
 33     key_t key_shm;
 34     key_t key_sem;
 35     int shmid;
 36     int semid;
 37     char *shmaddr;
 38     
 39     key_shm = ftok(".", 1);
 40     key_sem = ftok(".", 2);
 41     
 42     shmid = shmget(key_shm, 1024 * 4, IPC_CREAT | 0777);
 43     semid = semget(key_sem, 1, IPC_CREAT | 0777); 
 44     
 45     union semun set;
 46     set.val = 1;  // 为1 是有 锁,为0是无
 47     shmaddr = (char *)shmat(shmid, 0, 0);
 48     semctl(semid, 0, SETVAL,set);
 49     while (1) {
 50         Psem(semid);
 51         strcpy(shmaddr,"this msg form boy");
 52         Vsem(semid);
 53         sleep(1);
 54         printf("%s\n",shmaddr);
 55     }   
 56 ....
 57     shmdt((void *)shmaddr);
 58     
 59     shmctl(shmid,IPC_RMID,NULL);
 60     semctl(semid,0,IPC_RMID,NULL);
 61     return 0;
 62 }   

// shmandsem_girl.c
  1 #include <stdio.h>
  2 #include <sys/ipc.h>
  3 #include <sys/shm.h>
  4 #include <sys/types.h>     
  5 #include <sys/sem.h>
  6 #include <string.h>
  7     
  8 union semun {
  9     int val;               /* Value for SETVAL */
 10     struct semid_ds *buf;  /* Buffer for IPC_STAT, IPC_SET */
 11     unsigned short *array; /* Array for GETALL, SETALL */
 12     struct seminfo *__buf; /* Buffer for IPC_INFO
 13                                                              (Linux-specific) */
 14 };  
 15     
 16 void Psem(int semid){
 17     struct sembuf set;
 18     set.sem_num = 0;
 19     set.sem_op = -1;
 20     set.sem_flg = SEM_UNDO;
 21     semop(semid,&set,1);
 22 }   
 23     
 24 void Vsem(int semid){
 25     struct sembuf set;
 26     set.sem_num = 0;
 27     set.sem_op = 1;
 28     set.sem_flg = SEM_UNDO;
 29     semop(semid,&set,1);
 30 }   
 31     
 32 int main() {
 33     key_t key_shm;
 34     key_t key_sem;
 35     int shmid;
 36     int semid;
 37     char *shmaddr;
 38     
 39     key_shm = ftok(".", 1);
 40     key_sem = ftok(".", 2);
 41     
 42     shmid = shmget(key_shm, 1024 * 4, IPC_CREAT | 0777);
 43     semid = semget(key_sem, 1, IPC_CREAT | 0777); 
 44     
 45     union semun set;
 46     set.val = 1;
 47     shmaddr = (char *)shmat(shmid, 0, 0);
 48     semctl(semid, 0, SETVAL,set);
 49     while (1) {
 50         Psem(semid);
 51         strcpy(shmaddr,"this msg form girl");
 52         Vsem(semid);
 53         sleep(1);
 54         printf("%s\n",shmaddr);
 55     }   
 56 
 57     shmdt((void *)shmaddr);
 58     
 59     shmctl(shmid,IPC_RMID,NULL);
 60     semctl(semid,0,IPC_RMID,NULL);
 61     return 0;
 62 }   

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值