Unix/Linux进程间通信

时间:2023-03-09 12:55:05
Unix/Linux进程间通信

一,Linux下进程间通信的几种主要手段简介:

1,管道(Pipe)及有名管道(named pipe)

  管道可用于具有亲缘关系进程间的通信

  有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

2,信号(Signal):,

3,消息队列(Message):

  消息队列是消息的链接表,包括Posix消息队列system V消息队列

  消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

4,共享内存:

  使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

5,信号量(Semaphore):

  主要作为进程间以及同一进程不同线程之间的同步手段。

6,套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。

二,管道

  参考https://www.cnblogs.com/cfans1993/p/5657478.html

    https://www.cnblogs.com/52php/p/5878029.html

    https://www.cnblogs.com/52php/p/5840229.html

  管道是Linux支持的最初Unix IPC形式之一,具有以下特点:

  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
  • 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
  • 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
  • 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

2.1 pipe

#include <unistd.h>
int pipe(int fd[2])

  • 专用于父子进程通信, 函数原型 int pipe(int fd[2])
  • fd[0]表示输入, fd[1]表示输出
  • 如果父子进程要双向通信, 可以通过类似信号的功能进行控制, 也可以简单地打开两个pipe
  • 用read,Wright 读写
  • 只有阻塞方式

当管道的写端存在时,数据不能超过PIPE_BUF 注:(不同系统要求的PIPE_BUF不一样,通常为512字节)。

2.2  popen和pclose

#include <stdio.h>
FILE* popen(const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);

    • 函数原型FILE *popen(const char *cmdstring, const char *type) , type的参数为"r"或"w"
    • 用于父子进程通信, popen会自动fork子进程、创建pipe和关闭不需要的pipe端
    • popen的实现有可理解为execl("/bin/sh","sh","-c",cmdstring,NULL)
    • 当父进程向子进程发送信息时(type="w"), 实际就是向shell发送命令;
      当父进程从子进程获取信息时(type="r"), 实际就是读取shell的执行结果
    • 虽然popen的返回是FILE,但关闭是要用pclose(fp)
    • 用fread 和fwright写
    • 只有阻塞方式
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h> #define MAXLINE 1024
void err_quit(const char *str){
perror(str);
exit(1);
}
int main(){
char line[MAXLINE];
FILE *fpin; if((fpin=popen("ls -l","r")) == NULL)
err_quit("popen error");
while(fgets(line,MAXLINE,fpin) != NULL){
if(fputs(line,stdout) ==EOF)
err_quit("fputs error");
}
if(ferror(fpin))
err_quit("fpin error");
if(pclose(fpin)==-1)
err_quit("pclose error");
return 0;
}

2.3 fifo

  • int mkfifo(const char *pathname,mode_t mode), 创建一个通信文件, 参数同open
  • mkfifo后, 以open打开文件pathname, 打开方式为只读或只写, 另外可以用阻塞或非阻塞方式打开
  • 以只读打开时, 函数会阻塞直到有进程以只写方式打开
  • 以只写打开时, 函数会阻塞直到有进程以只读方式打开
  • 可用于非父子进程通信, 不同的进程只需要将pathname设置为同一文件,就可以通信。双向通信时开需两个
  • 在一个进程中用mkfifo将一个文件设置为FIFO模式,同时open函数打开,read,Wright读写,另外一个进程只需要open和read,wright

 

三, 信号量

https://www.cnblogs.com/52php/p/5851570.html

3.1 信号量的工作原理

由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,

因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。

3.2、semget()函数

它的作用是创建一个新信号量或取得一个已有信号量,原型为:

int semget(key_t key, int num_sems, int sem_flags);

  key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,比如每个进程都调用:

  sem_id = semget((key_t) 1234, 1, 0666 | IPC_CREAT);就可以访问同一个信号量

3.3 semop()函数

它的作用是改变信号量的值,原型为:

  int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

四,共享内存

  参考:https://www.cnblogs.com/52php/p/5861372.html

4.1概念

  不同进程之间共享的内存通常安排为同一段物理内存。通常与信号量一起用。

4.2 函数

  int shmget(key_t key, size_t size, int shmflg);//不同的进程使用相同的key值,通常置为整形,就可以访问同一块共享内存

  void *shmat(int shm_id, const void *shm_addr, int shmflg);  // attach启动对该共享内存的访问,可以用memcpy直接操作空间

  int shmdt(const void *shmaddr);  //detach 该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。

  int shmctl(int shm_id, int command, struct shmid_ds *buf);  //用来控制共享内存

五,消息队列

5.1 概念

  消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 消息队列与命名管道一样,每个发送的数据块都有最大长度的限制。Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。

5.2 函数

int msgget(key_t, key, int msgflg);//创建和访问消息队列

int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);//把消息添加到消息队列中

int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);//从一个消息队列获取消息

int msgctl(int msgid, int command, struct msgid_ds *buf);//控制消息队列

六,socket

https://www.cnblogs.com/52php/p/5872596.html

6.1 概念

套接字的特性由3个属性确定,它们分别是:域、类型和协议。

套接字的域:

  AF_INET,它指的是Internet网络

  AF_UNIX表示UNIX文件系统,它就是文件输入/输出,而它的地址就是文件名。

套接字类型

  流套接字 SOCK_STREAM: TCP

  数据报套接字SOCK_DGRAM :UDP

6.2 套接字地址

  不同的域,有不同的套接字地址格式

  对于AF_UNIX域套接字来说,它的地址由结构sockaddr_un来描述,该结构定义在头文件sys/un.h中,它的定义如下:

1
2
3
4
struct sockaddr_un {  
    sa_family_t sun_family; // AF_UNIX,它是一个短整型  
    char        sum_path[]; // 路径名  
};

  对于AF_INET域套接字来说,它的地址结构由sockaddr_in来描述,它至少包括以下几个成员:

1
2
3
4
5
struct sockaddr_in {  
    short int            sin_family;//AF_INET  
    unsigned short int    sin_port;//端口号  
    struct in_addr        sin_addr;//IP地址  
};  

6.3 基于流套接字(tcp)的客户/服务器的工作流程

6.3.1 服务器

  socket-》bind-》listen-》accept

6.3.1 客户端

  socket-》connect

6.4 基于流套接字(tcp)的客户/服务器的工作流程

6.4.1 服务器

  socket-》bind-》recvfrom/sendto

6.4.1 客户端

  socket-》recvfrom/sendto

七,信号

https://www.cnblogs.com/52php/p/5813867.html

信号的名称是在头文件signal.h中定义的,信号都以SIG开头,常用的信号并不多,常用的信号如下:

Unix/Linux进程间通信

更多的信号类型可查看附录表。