在没有学习进程间通信的时候,两个进程可以通过文件进程通信。但是在使用文件通信的时候谁先执行谁后执行无法确定,所以这种通信方式有问题。在linux系统中的进程间通信方式有7种。

原始的通信方式:无名管道 有名管道 信号
system V的系统上引入如下三种方式:​消息队列 共享内存 信号灯
通过套接字进行本地进程间通信:BSD​ 

一、原始的通信方式

1.无名管道

①无名管道的原理

无名管道只能用于具备亲缘关系的进程间通信,无名管道是一个半双工的通信方式。

        单工: A----->B

        半双工:在同一时间点内只能一端发另外一端收    A------>B B----->A

        全双工:在同一时刻内可以双向发送和接收    A<------->B

管道最大是64K,无名管道不能使用lseek定位光标。

②无名管道读写端特点

读端存在,写管道 :有多少写多少,直到管道写满(64K)

读端不存在,写管道:读端不存在写管道没有意义,管道破裂,操作系统给进程发送一个SIGPIPE,杀死进程

写端存在,读管道 :有多少读多少,如果管道里没有数据,读阻塞等待

写端不存在,读管道:有多少读多少,如果管道里没有数据,立即ansh

③用到的函数
int pipe(int pipefd[2]);
功能:创建一个无名管道

#include <unistd.h>
参数:
        @pipefd:返回的是读写段的文件描述符
返回值:成功返回0,失败返回-1置位错误码

示例:

#include <head.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    int pipefd[2];
    char buf[1024] = {0};
    pid_t pid;
    //1.创建管道
    if(pipe(pipefd) == -1)
        PRINT_ERR("pipe create error");

    //2.创建进程
    pid = fork();
    if(pid == -1){
        PRINT_ERR("fork error");
    }else if(pid == 0 ){
        close(pipefd[0]); //关闭读取端
        //1.子进程
        while(1){
            fgets(buf,sizeof(buf),stdin);
            buf[strlen(buf)-1]='\0';

            write(pipefd[1],buf,strlen(buf));

            if(strncmp(buf,"quit",4)==0)
                break;
        }
        printf("子进程退出了\n");
        close(pipefd[1]);
        exit(EXIT_SUCCESS);

    }else{
        close(pipefd[1]); //关闭写端口
        //2.父进程
        while(1){
            memset(buf,0,sizeof(buf)); //bzero
            read(pipefd[0],buf,sizeof(buf));

            if(strncmp(buf,"quit",4)==0)
                break;

            printf("parent buf = %s\n",buf);
        }
        printf("父进程退出了\n");
        close(pipefd[0]);
        wait(NULL);
    }
    return 0;
}
//head.h

#ifndef __HEAD_H__
#define __HEAD_H__
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
#define PRINT_ERR(msg) do{ \
        perror(msg);\
        return -1;\
    }while(0)
 
#endif
————————————————
版权声明:本文为CSDN博主「zhangts318」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhangts318/article/details/123973935

2.有名管道

有名管道可以用于任意进程间的通讯,不一定是亲缘关系的进程。有名管道的大小也是64K,也不能使用lseek函数。

①有名管道通信的特点

读不存在写管道:在打开的位置阻塞

写不存在读管道:在打开的位置阻塞

先存在读,然后关掉读,写管道:管道破裂,收到SIGPIPE信号

先存在写,然后关掉写,读管道:读端立即返回

如果读写都正常存在:有多少写多少,直到写满64K为止,有多少读多少,没有数据就阻塞

②用到的函数

int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道

#include <sys/types.h>
#include <sys/stat.h>
参数:
        @pathname:路径及有名管道的文件
                管道文件不是在磁盘上存储的,管道文件是在内存上存储的。
                管道文件的大小永远都是0,不管有没有向里面写数据,只起到标识作用
        @mode:管道文件的权限
返回值:成功返回0,失败返回-1置位错误码

//当文件创建成功之后,可以通过open read write close进行通信

示例:

//创建管道
#include <head.h>
#define FIFO "./fifo1"
int main(int argc, char const *argv[])
{
    char buf[20] = {0};
    if (mkfifo(FIFO, 0664) == -1)
        PRINT_ERR("make fifo error");

    while (1)
    {
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';
        if (strncmp(buf, "quit", 4) == 0)
            break;
    }

    printf("销毁管道文件..\n");

    system("rm ./fifo1");

    return 0;
}
//发送方
#include <head.h>
#define FIFO "./fifo1"
int main(int argc, char const *argv[])
{
    int fd;
    char buf[1024] = {0};
    if((fd = open(FIFO,O_WRONLY))==-1){
        PRINT_ERR("open error");
    }
    while(1){
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';
        write(fd,buf,strlen(buf));
        if (strncmp(buf, "quit", 4) == 0)
            break;

    }

    close(fd);
    return 0;
}
//接收方
#include <head.h>
#include <strings.h>
#define FIFO "./fifo1"
int main(int argc, char const *argv[])
{
    int fd;
    char buf[1024] = {0};
    if((fd = open(FIFO,O_RDONLY))==-1){
        PRINT_ERR("open error");
    }

    while(1){
        bzero(buf,sizeof(buf));
        read(fd,buf,sizeof(buf));
        if (strncmp(buf, "quit", 4) == 0)
            break;
        printf("buf = %s\n",buf);
    }

    close(fd);
    return 0;
}

3.信号

信号是linux内核中一种软件模拟硬件中断的机制,如果没有linux内核就没有信号,但是会存在中断。用户可以给进程发信号,进程可以给进程发信号,内核可以给进程发信号。进程收到信号的处理方式默认,忽略,捕捉

①查看Linux中的信号:kill -l

 常用的信号:

注:在所有的信号中只有SIGKILL和SIGSTOP不能被捕捉也不能被忽略,其他的信号如果是默认处理的,大部分都是杀死进程。

②用到的函数:signal/kill/raise/alarm

typedef void (*sighandler_t)(int);  //信号处理函数的原型
void handle(int signo)
{
  //信号处理函数的原型   
}

sighandler_t signal(int signum, sighandler_t handler);
功能:注册信号处理函数

#include <signal.h>
参数:
        @signum:信号号
        @handler:处理函数
                SIG_IGN :忽略
                SIG_DFL :默认
                handle :捕捉
返回值:成功返回handler,失败返回SIG_ERR,置位错误码

示例:

#include <head.h>
#include <signal.h>
void handle(int signo)
{
    if (signo == SIGINT)
    {
        printf("收到了ctrl+c\n");
    }
}
int main(int argc, char const *argv[])
{
    // 1.默认处理,杀死程序
    //  if(SIG_ERR == signal(SIGINT,SIG_DFL))
    //  PRINT_ERR("signal error");

    // 2.忽略处理,ctrl+c没有任何反应
    //  if (SIG_ERR == signal(SIGINT, SIG_IGN))
    //      PRINT_ERR("signal error");

    // 3.捕捉
    if (SIG_ERR == signal(SIGINT, handle))
        PRINT_ERR("signal error");

    while (1);
    return 0;
}

int kill(pid_t pid, int sig);
功能:给指定的进程发信号

#include <sys/types.h>
#include <signal.h>
参数:
        @pid:进程号
                pid>0:给指定pid进程发信号
                pid=0: 给当前进程组内的进程发信号
                pid = -1 :给所有的进程发信号(必须有权限)
                pid <-1 :将pid取反,将信号发给取反之后对应的进程组
        @sig:信号号
返回值:成功返回0,失败返回-1置位错误码    


int raise(int sig);
功能:给当前进程发信号

#include <sys/types.h>
#include <signal.h>
参数:
        @sig:信号号
返回值:成功返回0,失败返回非0 

示例:

#include <head.h>
#include <sys/wait.h>
#include <signal.h>
void handle(int signo)
{
    printf("get SIGUSR1 signal from child\n");
    waitpid(-1,NULL,WNOHANG);
    raise(SIGKILL);
}
int main(int argc, char const *argv[])
{
    pid_t pid;
    pid = fork();
    if(pid < 0){
        PRINT_ERR("fork error");
    }else if(pid ==0){
        sleep(5);
        printf("子进程退出了\n");
        kill(getppid(),SIGUSR1);

    }else{
        signal(SIGUSR1,handle);
        while(1);
    }
    return 0;
}

闹钟信号alarm:
unsigned int alarm(unsigned int seconds);
功能:在seconds后发送闹钟信号

#include <unistd.h>
参数:
        @seconds:倒计时的秒钟
                如果在倒计时到0前又重新给seconds赋值,重新计数seconds秒
                如果第一次就是seconds赋值为0,就不会发SIGALRM信号了
返回值:返回上一次倒计时为0前的剩余的秒钟,如果是0表示之前没有调闹钟的函数

示例:

#include <head.h>
#include <signal.h>
void handle(int signo)
{
    printf("自动出牌了=t\n");
    alarm(4); //如果第一次都不输入任何的字符,可以在自动出牌后开启下一次计时
}
int main(int argc, char const *argv[])
{
    if (SIG_ERR == signal(SIGALRM, handle))
        PRINT_ERR("signal error");

    alarm(4);//倒计时+kill(getpid(),SIGALRM)
    char ch;
    while(1){
        ch = getchar();
        getchar();
        printf("手动出牌是=%c\n",ch);
        alarm(4);
    }
    return 0;
}

二、system V IPC进程间通讯

消息队列  共享内存  信号量

查看相关的命令:ipcs 查看所有的信息(消息队列,共享内存,信号量)

​                             ipcs -q 消息队列

​                             ipcs -m 共享内存

​                             ipcs -s 信号量(信号灯)(semphore)

删除相关的命令:​ ipcrm -q/-m/-s ID 删除

1.消息队列

①消息队列的原理

 ②用到的函数:ftok/msgget/msgsnd/msgrcv/msgctl

头文件均为:#include <sys/ipc.h>
                     #include <sys/msg.h>

key_t ftok(const char *pathname, int proj_id);
功能:获得key值  
参数:
        @pathname 已经存在的文件路径
        @proj_id 获取这个整数的低8bit (key = proj_id,inode,st_dev)
返回值:成功返回 key值,失败返回-1 

第二种方法:将key值指定为IPC_PRIVATE ,当IPC对象在亲缘关系进程通信的时候


int msgget(key_t key, int msgflg);
功能:创建IPC对象 
参数:
        @key IPC_PRIVATE 或 ftok
        @msgflg IPC_CREAT | 0666 或 IPC_CREAT | IPC_EXCL | 0666 (判断IPC对象是否存在) 
返回值:成功返回ID,失败返回-1 

注意:如果对应key值的IPC对象不存在,则创建,如果存在,直接返回IPC对象的ID 


int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:发送消息
参数:
        @msqid 消息队列ID 
        @msgp 需要发送的消息存放的地址
        @msgsz 消息正文的大小
        @msgflg     0:阻塞的方式发送 IPC_NOWAIT:非阻塞方式调用
返回值:成功返回0,失败返回-1 (队列的大小限制MSGMNB 16384)

消息结构体定义:
typedef struct{
         long msg_type;   //消息类型必须在第一个位置,
         char mtxt[1024];
         ...
}msg_t;
正文大小:sizeof(msg_t) - sizeof(long)


ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收消息 
参数:
        @msqid 消息队列ID 
        @msgp 存放接收到的消息 
        @msgsz 正文大小 
        @msgtyp 消息类型 ,  0: 总是从消息队列中提取第一个消息
        @msgflg     0:阻塞的方式接收 IPC_NOWAIT:非阻塞方式调用
返回值:成功返回 接收消息正文大小,失败返回-1


int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:删除消息队列
参数:
        @msgqid 消息队列 
        @cmd     IPC_RMID(删除消息队列)  IPC_SET(设置消息队列的属性信息) IPC_STAT(获取消息队列属性信息)
        @buf 存放消息队列属性 
返回值:成功返回0,失败返回-1

示例:

//发送方
#include <head.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct
{
    long msg_type; //消息类型必须在第一个位置,
    char name[20];
    int age;
    char sex;
} msg_t;
int main(int argc, char const *argv[])
{
    key_t key;
    int msgid;

    // 1.获取key
    if ((key = ftok("/home/linux", 'y')) == -1)
        PRINT_ERR("ftok error");
    // 2获取消息队列
    if ((msgid = msgget(key, IPC_CREAT | 0664)) == -1)
        PRINT_ERR("msg get error");

    msg_t m1 = {1, "zhangsan", 50, 'm'};
    msgsnd(msgid, &m1, sizeof(msg_t) - sizeof(long), 0);
    msg_t m2 = {2, "lisi", 20, 'm'};
    msgsnd(msgid, &m2, sizeof(msg_t) - sizeof(long), 0);
    msg_t m3 = {3, "wangwu", 18, 'w'};
    msgsnd(msgid, &m3, sizeof(msg_t) - sizeof(long), 0);

    
    return 0;
}
//接收方
#include <head.h>
#include <sys/ipc.h>
#include <sys/msg.h>
typedef struct
{
    long msg_type; //消息类型必须在第一个位置,
    char name[20];
    int age;
    char sex;
} msg_t;
int main(int argc, char const *argv[])
{
    key_t key;
    int msgid;

    // 1.获取key
    if ((key = ftok("/home/linux", 'y')) == -1)
        PRINT_ERR("ftok error");
    // 2获取消息队列
    if ((msgid = msgget(key, IPC_CREAT | 0664)) == -1)
        PRINT_ERR("msg get error");
    msg_t msg;
    while(1){
        bzero(&msg,sizeof(msg_t));
        msgrcv(msgid,&msg,sizeof(msg_t)-sizeof(long),2,0);
        printf("name =%s,age = %d,sex = %c\n",
            msg.name,msg.age,msg.sex);
    }
    return 0;
}

2.共享内存

①共享内存内部原理

②用到的函数:shmget/shmat/shmdt/shmctl

头文件均为:#include <sys/ipc.h>
                     #include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
功能:获取共享内存段的ID,创建共享内存
参数:
        @key IPC_PRIVATE 或 ftok() 
        @size 申请的共享内存段大小 [4k的倍数]
        @shmflg IPC_CREAT | 0666 或 IPC_CREAT | IPC_EXCL 
返回值:成功返回ID,失败返回-1 


void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:映射共享内存到用户空间 
参数:
        @shmid 共享内存段ID 
        @shmaddr  NULL:系统自动完成映射 
        @shmflg SHM_RDONLY:只读   0:读写
返回值:成功返回映射后的地址,失败返回(void *)-1 


int shmdt(const void *shmaddr);
功能:撤销映射
参数:
        @shmaddr 共享内存映射的地址 

注意:当一个进程结束的时候,它映射共享内存,会自动撤销映射


int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmctl(shmid,IPC_RMID,NULL);
功能:根据命令控制共享内存,删除IPC对象
参数:
        @shmid 共享内存段的ID 
        @cmd IPC_STAT[获取属性],IPC_SET[设置属性],IPC_RMID[删除IPC对象]
        @buf 保存属性 
返回值:成功返回0,失败返回 -1 

注意:当我们调用shmctl删除共享内存的时候,并不会立即删除。只有当共享内存映射次数为0,才会删除共享内存对象

示例:

//发送方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main(int argc, char const *argv[])
{
    key_t key;
    int shmid;
    char * addr;
    // 1.获取key
    if ((key = ftok("/home/linux", 'y')) == -1)
        PRINT_ERR("ftok error");
    // 2获取共享内存
    if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)
        PRINT_ERR("shm get error");

    addr = shmat(shmid,NULL,0);
    if(addr == (void *)-1){
        fprintf(stderr,"shm at error");
        return -1;
    }

    while(1){
        fgets(addr,4096,stdin);
        addr[strlen(addr)-1]='\0'; 
    }
    //撤销映射
    shmdt(addr);

} 
//接收方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main(int argc, char const *argv[])
{
    key_t key;
    int shmid;
    char * addr;
    // 1.获取key
    if ((key = ftok("/home/linux", 'y')) == -1)
        PRINT_ERR("ftok error");
    // 2获取共享内存
    if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)
        PRINT_ERR("shm get error");

    addr = shmat(shmid,NULL,0);
    if(addr == (void *)-1){
        fprintf(stderr,"shm at error");
        return -1;
    }

    while(1){
        sleep(3);
        printf("addr = %s\n",addr);
    }

    //撤销映射
    shmdt(addr);
}      

3.进程的同步:信号量(信号灯)

POSIX 线程中的同步用的是无名信号量 
进程间的同步使用的是IPC 对象[信号灯集]

信号灯集:信号灯集合,每一个信号灯都可以用来表示一类资源,其值表示资源的个数

用到的函数:sema_init/P/V(自行封装的,源代码如下)

//源代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <head.h>

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) */
};
int init_sem_value(int sem_id,int sem_num,int value)
{
 union semun sem_val;
 
	sem_val.val = value;
 
 if(semctl(sem_id,sem_num,SETVAL,sem_val) < 0)
	{
		PRINT_ERR("semctl error");
	}

 return 0;
}
int sema_init(int num)
{
    key_t key;
    int semid;

    // 1.获取key
    if ((key = ftok("/home", 'u')) == -1)
        PRINT_ERR("ftok error");
    // 2获取共享内存
    if((semid = semget(key,num,IPC_CREAT|0664))==-1)
        PRINT_ERR("semget  error");

    init_sem_value(semid,0,0);
    init_sem_value(semid,1,1);
    return semid;
}
//P申请资源-1
int P(int sem_id,int sem_num)
{
 struct sembuf sem;

	sem.sem_num = sem_num;
	sem.sem_op  = -1;
	sem.sem_flg = 0;
 
 if(semop(sem_id,&sem,1) < 0)
	{
		PRINT_ERR("semp error");
	}
}
//V操作释放资源+1
int V(int sem_id,int sem_num)
{
 struct sembuf sem;

	sem.sem_num = sem_num;
	sem.sem_op  = 1;
	sem.sem_flg = 0;
 
 if(semop(sem_id,&sem,1) < 0)
	{
		PRINT_ERR("semv error");
	}
}
//函数声明
#ifndef __SEM_H__
#define __SEM_H__
int sema_init(int num);
int P(int sem_id,int sem_num);
int V(int sem_id,int sem_num);

#endif

示例:

//发送方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h"
int main(int argc, char const *argv[])
{
    key_t key;
    int shmid;
    int semid;
    char * addr;
    //0.初始化信号灯
    semid = sema_init(2);
    // 1.获取key
    if ((key = ftok("/home/linux", 'y')) == -1)
        PRINT_ERR("ftok error");
    // 2获取共享内存
    if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)
        PRINT_ERR("shm get error");

    addr = shmat(shmid,NULL,0);
    if(addr == (void *)-1){
        fprintf(stderr,"shm at error");
        return -1;
    }

    while(1){
        P(semid,1);
        fgets(addr,4096,stdin);
        addr[strlen(addr)-1]='\0';
        V(semid,0);
    }

    //撤销映射
    shmdt(addr);

}       
//接收方
#include <head.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "sem.h"
int main(int argc, char const *argv[])
{
    key_t key;
    int shmid,semid;
    char * addr;
    semid = sema_init(2);
    // 1.获取key
    if ((key = ftok("/home/linux", 'y')) == -1)
        PRINT_ERR("ftok error");
    // 2获取共享内存
    if((shmid = shmget(key,4096,IPC_CREAT|0664))==-1)
        PRINT_ERR("shm get error");

    addr = shmat(shmid,NULL,0);
    if(addr == (void *)-1){
        fprintf(stderr,"shm at error");
        return -1;
    }

    while(1){
        P(semid,0);
        printf("addr = %s\n",addr);
        V(semid,1);
    }

    //撤销映射
    shmdt(addr);

}       
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐