进程间的五种通信方式总结

一、 管道概念

管道:通常指无名管道,是UNIX系统IPC最古老的形式。它是半双工(即数据只能在一个方向流动,类似水流从水管流过。),具有固定的读端和写端。适用于有亲缘关系的进程间通信。

二、如何创建管道

原型:
1.#include <stdio.h>
#include <unistd.h>
2. int pipe(int pipefd[2]);
当管道创建成功时返回0失败返回-1.
会创建两个文件描述符fd[0]为读端,fd[1]为写端。
下面展示一些 管道创建流程
#include <stdio.h>
#include <unistd.h>

#include <string.h>
#include <stdlib.h>
int main()
{
int fd[2];
int pid;
char buf[128];
// int pipe(int pipefd[2]);//创建管道
if(pipe(fd) == -1){
printf(“creat pipe failed\n”);
}

    pid = fork();//创建父子进程
    if(pid < 0){
            printf("creat child failed\n");
    }else if(pid > 0){
            sleep(3);
            printf("this is father\n");
            close(fd[0]);
            write(fd[1],"the message from father",strlen("the message from father"));
            wait();
    }else{
            printf("this is child\n");
            close(fd[1]);
            read(fd[0],buf,128);
            printf("read from father:%s\n",buf);
            exit(0);
    }

    return 0;

}

有名管道FIFO

FIFO,也称命名管道,它是一种文件类型。可适用于无关进程之间的通讯,与无名管道不同。它有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
原型
#include <sys/types.h>
#include <sys/stat.h>

   int mkfifo(const char *pathname, mode_t mode);

//返回值;成功返回0,出错返回-1

其中mode参数与open函数中的mode相同,一旦创建一个FIFO,就可以用一般文件I/O函数来操作它。
当open一个FIFO时,是否设置非阻塞标志(O-NONBLOCK)的区别:
1.如果没有指定O-NONBLOCK(默认),只读open要阻塞到某个其他进程为写而打开此FIFO。类似的只写open要阻塞到某个其他进程为读而打开它。
2.若指定了O_NONBLOCK,则只读open立即返回。而只写open将出错返回-1如果没有进程已经为读而打开该FIFO,其errno置ENXIO。
open函数mode参数详解
int open(const charpathname,int flags);
int open(const char
pathname,int flags,mode_t mode);
参数说明:
1.pathname
要打开或创建的目标文件
2.flags
打开文件时,可以传入多个参数选项,用下面的
一个或者多个常量进行“或”运算,构成falgs
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR: 读,写打开
这三个常量,必须制定一个且只能指定一个
O_CREAT: 若文件不存在,则创建它,需要使
用mode选项。来指明新文件的访问权限
O_APPEND: 追加写,如果文件已经有内容,这次打开文件所
写的数据附加到文件的末尾而不覆盖原来的内容
返回值:
成功:新打开的文件描述符
失败:-1
open返回的文件描述符一定是最小的而且没有被使用的

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

//       int mkfifo(const char *pathname, mode_t mode);

int main()
{
        int ret = mkfifo("./file",0600);//创建FIFO
        if(ret == 0){
                printf("creat FIFO success\n");
        }
        if(ret == -1){
                printf("creat failed\n");
                perror("why:");
        }
        return 0;
}    

消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
1.特点:
1.消息队列是面向记录的,其中的信息具有特定的格式以及特定的优先级。
2.消息队列独立于发送和接收进程。进程终止时,消息队列及其内容并不会删除。
3.消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
2.原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int msgflg);

//添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

//读取消息:成功返回消息数据的长度,失败返回-1. msgflg通常为0,msgtyp为结构体mtype。msgsz为调用结构体的mtext。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);

//控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds buf);
cmd:IPC_RMID消息队列中的链表移除 buf:NULL
参数:void msgp
struct msgbuf {
long mtype; /
message type, must be > 0 /
char mtext[1]; /
message data */
};

在以下 两种情况下,msgget将创建一个新的消息队列:
1.如果没有与键值key相对应的消息队列,并且flag中包含了IPC-CREAT标志位。
2.key参数为IPC_PRIVATE。
函数msgrcv在读取消息队列时,type参数有下面几种情况:
1.type == 0,返回队列中的第一个信息;
2.type > 0,返回队列中消息类型为type的第一个消息;
3.type < 0 ,返回队列中消息类型值小于或等于type绝对值的信息,如果有多个,则取类型值最小的消息。

下面展示一些 消息队列

//接收代码段
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
/* ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                      int msgflg);
*/
struct msgbuf {
      long mtype;       /* message type, must be > 0 */
      char mtext[128];    /* message data */
};

int main()
{
        struct msgbuf readbuf;
        //1.huoqu
        int msgID = msgget(0x1235,IPC_CREAT|0777);
        if(msgID == -1){
                printf("get que faile\n");
        }

        msgrcv(msgID,&readbuf,sizeof(readbuf.mtext),888,0);
        printf("read from que: %s\n",readbuf.mtext);

        return 0;
}
//发送代码段
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
/* ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                      int msgflg);
 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

*/
struct msgbuf {
      long mtype;       /* message type, must be > 0 */
      char mtext[128];    /* message data */
};

int main()
{
        struct msgbuf sendbuf = {888,"this is message from quen"};
        //1.huoqu
        int msgID = msgget(0x1235,IPC_CREAT|0777);
        if(msgID == -1){
                printf("get que faile\n");
        }

        msgsnd(msgID,&sendbuf,sizeof(sendbuf.mtext),0);

        return 0;
}

ftok函数

系统建立IPC通讯(消息队列、信号量和共享内存)时必须指定一个ID值。通常情况下,该ID值通过ftok函数得到。

ftok原型

#include <sys/types.h>
 #include <sys/ipc.h>
   key_t ftok(const char *pathname, int id);

pathname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:key_t key;
key = ftok(’.’,1) 这样就是将pathname设置为当前目录
id是子序号

共享内存

原型

#include <sys/ipc.h>
#include <sys/shm.h>
//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1.
int shmget(key_t key, size_t size, int shmflg);
//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1.
shmaddr 一般填写0让系统自己查找 shmflg 一般填写0映射进来的内存可读可写。通常创建一个指针来接收shmat.如:
char *shmaddr = shmat();
void *shmat(int shmid, const void *shmaddr, int shmflg);

//断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(const void *shmaddr);

//控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
buf 写0。

当使用shmget函数创建一段共享内存时,必须指定其size;而如果引用一个已存在的共享内存,则将size指定为0。
当一段共享内存被创建后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
shmdt函数是用来断开shmat建立的连接。注意,这并不是删除共享内存,只是当前进程不能在访问该共享内存而已。
shmctl函数可以对共享内存执行多种操作,根据参数cmd执行相应的操作。常用IPC_RMID(从系统中删除该共享内存)。

ipcs -m 查看共享内存
ipcrm -m + shmid 删除该id下的共享内存

信号(signal)

什么是信号

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

信号概述

1.信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。具体的信号名称可以使用kill -l来查看信号的名字及序号,信号从1开始编号的,不存在0号信号。kill对于信号0有特殊的应用。
信号
信号的处理:
信号的处理有三种方法:忽略、捕捉、默认动作
忽略信号,大多数可以使用这个方式处理,但是有两种信号不能被忽略(分别是SIGKILL和SIGSTOP).因为他们向内核和超级用户提供进程终止和停止的可靠方法,如果忽略,那么这个进程变成没人能管理的进程。
捕捉信号,需要告诉内核,用户希望如何处理某一信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。该信号产生时,由内核调用用户定义的函数,以此来实现某种信号处理。
系统默认动作,对于每个信号来说,系统都应对默认的处理动作,当发生了该信号,系统会自动执行。不过对于系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。

原型

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

   sighandler_t signal(int signum, sighandler_t handler);

signum:可以由kill -l查看
handler:调用的函数。
ps -aux|grep +查找的名称:

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

/*typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
*/
void handler(int signum)
{
        printf("get signal:%d\n",signum);
        printf("never quie\n");

}
int main()
{
        signal(SIGINT,handler);//捕获ctrl+c信号
        while(1);
        return 0;
}

atoi(): 将字符串转换成整型数

kill函数

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

   int kill(pid_t pid, int sig);

pid;进程都pid号。
sig:需要处理的信号如SIGKILL、SIGINT等。
下面展示一些 kill 函数的使用

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

/*#include <sys/types.h>

int kill(pid_t pid, int sig);
*/

/*typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
*/
int main(int argc,char **argv)
{
        int pid = atoi(argv[1]);
        int signum = atoi(argv[2]);
        printf("pid = %d signum = %d\n",pid,signum);
        kill(pid,signum);

        return 0;
}

信号量(semaphore)

信号量与已经介绍过的IPC结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
1.特点:
1.信号量用于进程间同步,若要在进程间传递数据需要配合共享内存。
2.信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作。
3.每次对信号量的PV操作不仅限于对信号值加一减一,而且可以加减任意正整数。
4.支持信号量组。
最简单的信号量只能取0和1的变量,这也是信号量最常见的一种形式,叫做二值信号量。而可以取多个正整数的信号量被称为通用信号量。
Linux下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
2.原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//创建或者获取一个信号量组:若成功返回信号量集ID,失败返回-1。nsems:创建信号量的个数 semflg:IPC_CREAT。
int semget(key_t key, int nsems, int semflg);
//对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
//控制信号量的相关信息
int semctl(int semid, int semnum, int cmd, …);
当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1;如果引用一个现有的集合,则将num_sems指定为0。
```javascript
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/*int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd, …);
*/

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

9 void pGetKey(int id)
20 {
21 struct sembuf set;
22 set.sem_num = 0;
23 set.sem_op = -1;
24 set.sem_flg = SEM_UNDO;
25
26 semop(id,&set,1);
27 printf(“getkey\n”);
28
29
30 }
31 void vPutBackKey(int id)
32 {
33 struct sembuf set;
34 set.sem_num = 0;
35 set.sem_op = 1;
36 set.sem_flg = SEM_UNDO;
37
38 semop(id,&set,1);
39 printf(“put back the key\n”);
40
41
42 }

int main(int argc,char *argv[])
{
key_t key;
int semid;
key = ftok(".",2);
//1 表示信号量集合中有一个信号量
semid = semget(key,1,IPC_CREAT|0666);//创建或者获取信号量
union semun initsem;
initsem.val = 0;
semctl(semid,0,SETVAL,initsem);//0:表示操作第0个信号量,//SETVAL:设置信号量的值,设置为inisem
int pid;
54 pid = fork();
55 if(pid > 0){
56 pGetKey(semid);
57 printf(“this is father\n”);
58 vPutBackKey(semid);
59 }
60 else if(pid == 0){
61 printf(“this is child\n”);
62 vPutBackKey(semid);
63 }else{
64 printf(“fork errno\n”);
65 }

    return 0;

}

## 什么是临界资源
多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值