Linux 进程间通信

进程间通信

进程间通信机制:

  • unix继承:管道、信号

  • system V IPC对象:共享内存、消息队列、信号灯集

  • 套接字

传统的UNIX进程间通信方式包括无名管道、有名管道以及信号。

System V进程间通信(IPC)包括System V消息队列、System V信号量以及System V共享内存。

现在在Linux中使用较多的进程间通信方式主要有以下几种:

1)无名管道 及 有名管道

2)信号

3)共享内存

4)消息队列

5)信号灯集

6)套接字。这是一种使用更广泛的进程间通信机制,它可用于网络中不同主机之间的进程间通信,应用非常广泛。本篇文章主要介绍前五种进程通信方式,套接字将在之后的网络编程中讲到。

1、管道

管道分为 无名管道 和 有名管道,区别在于创建的管道能否在文件系统中是否可见。

1)无名管道
特点:
  1. 创建之后在文件系统中不可见

  2. 半双工的方式进行通信

  3. 拥有固定的读端写端

  4. 只能用于具有亲缘关系的进程间通信

①无名管道的创建 pipe()
#include 
int pipe(int pipefd[2];
参数:
	pipefd:		存放无名管道读端 和 写端的数组首地址
 		pipefd[0]:	读端
 		pipefd[1]:	写端
      
返回值:
	成功返回 0, 失败返回 -1;

练习: 在一个进程中创建一个子进程,子进程从键盘获取数据,父进程打印输出

② 无名管道的读写特性

读特性:

写端存在:

  • 管道有数据: 返回读到的字节数

  • 管道无数据: 阻塞

写端不存在:

  • 管道有数据: 返回读到的字节数

  • 管道无数据: 返回 0

写特性:

读端存在:

  • 管道有空间: 返回写入的字节数

  • 管道无空间: 阻塞,直到有空间为止

读端不存在:

  • 无论管道有无空间,管道破裂

练习: ① 计算无名管道空间大小

​ ②验证管道破裂

2)有名管道 - 创建的管道在文件系统中 可见
  • 有名管道创建成功后会在文件系统中以管道文件的形式存在
  • 有名管道可以用于任意两个进程间通信,没有固定的读端和写端

① 有名管道的创建 mkfifo()

#include 
#include 

int mkfifo(const char *pathname, mode_t mode);
参数:
	pathname:	创建管道文件的文件名
    mode:		创建管道文件的权限

返回值:
	成功返回 0, 失败返回 -1;

练习: 创建一个有名管道,一个进程向管道中输入数据,另一个进程输出数据

2、信号

信号:是中断在软件层次上的一种模拟

信号的处理方式:

  • 默认处理

  • 忽略

  • 捕获信号

1)常用信号
2)信号相关指令
kill -l					查看当前系统中的所有信号
kill -信号编号 -进程号		向指定进程发送对应编号的信号
  eg.
	kill -9 1123		向进程  1123发送信号9
    kill -9 -1123		向进程组 1123发送信号9
    kill -9 -1			向除了init进程以外的其他所有进程发送信号9
3)信号相关接口函数

kill() / raise()

#include 
#include 

int kill(pid_t pid, int sig);
参数:
	pid:	指定进程号
	sig:	指定信号
返回值:
	成功返回 0,失败返回 -1----------------------------------------------------------------

#include 
int raise(int sig);
//发送信号给本进程
参数:
	sig:	指定信号
返回值:
	成功返回 0,失败返回 非零
4)定时器

定时器时间到,当前进程会接收到编号为14的信号 – SIGALRM

  • 一个进程中最多只能存在一个定时器

alarm() / pause()

#include 
unsigned int alarm(unsigned int seconds);
参数:
	seconds:	表示定时秒数
返回值:
	成功返回 0,或者上一个定时器定时剩余时间
-------------------------------------------

#include 
int pause(void);
功能:
	阻塞当前进程,等待定时器结束
5)捕获信号 signal()
#include 
typedef void (*sighandler_t)(int);
//void func1();
sighandler_t sinal(int signum, sinhandler_t handler);
	eg.
        sinal(SIGALRM, func1);
参数:
	signum:		指定信号
	handler:	信号处理函数
		SIG_IGN:	选择以忽略方式处理指定信号
		SIG_DFL:	选择以默认方式处理指定信号
返回值:

练习:创建一个子进程,子进程结束时,父进程提示子进程退出信息

3、system V IPC对象

ipcs: 查看IPC对象

所有的IPC对象只能通过用户显式地进行删除 ipcrm命令

1)共享内存

共享内存是进程间通信效率最高的一种通信机制。

key 值的获取
#include 
#include 
key_t ftok(const char *pathname, int proj_id);

参数:
	pathname:	任意路径
    proj_id:	任意字符
返回值:
	成功返回 key值,失败返回 -1
使用共享内存流程:

1.创建或者打开共享内存 – shmget()

#include 
#include 
int shmget(key_t key, size_t size, int shmflg);
参数:
	key:	通过ftok得到或者使用IPC_PRIVATE(创建私有共享内存)
    size:	共享内存的大小
    shmflg:	创建共享内存权限,一般填0664 | IPC_CREAT

返回值:
	成功返回 共享内存ID,失败返回 -1

2.映射共享内存 – shmat()

#include 
#include 
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
	shmid:		共享内存id
    shmaddr:	映射地址,填NULL表示系统自动分配
    shmflg:		
		SHM_RDONLY	只读
		0			读写

返回值:
	成功返回映射地址, 失败返回 (void *)-1

3.访问共享内存

4.取消映射 – shmdt()

#include 
#include 

int shmdt(const void *shmaddr);
参数:
	shmaddr:	映射地址
返回值:
	成功返回 0,失败返回 -1

5.操作共享内存(查/删/改) – shmctl()

#include 
#include 
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数:
	shmid:	共享内存id号
	cmd:	操作共享内存的命令
       IPC_STAT:	获取共享内存的信息,需要用到第三个参数
		IPC_SET:	设置共享内存信息
		IPC_RMID:	删除共享内存,第三个参数填NULL;
返回值:
	成功返回 0,失败返回 -1

练习: 一个进程输入数据,一个进程打印输出数据

2)消息队列
消息队列的使用流程:

1.创建、打开消息队列 – msgget()

#include 
#include 
#include 

int msgget(key_t key, int msgflg);
参数:
	key:	通过ftok得到的key值或者使用IPC_PRIVATE创建私有消息队列
    msgflg:	创建消息队列的权限,一般填 0664|IPC_CREAT
返回值:
	成功返回消息队列id,失败返回 -1;

eg.
    int msgid = msgget(IPC_PRIVATE, 0664);

2.发送消息 – msgsnd()

#include 
#include 
#include 

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数:
	msgid:		消息队列id
	msgp:		发送消息相关的结构体的首地址
	msgsz:		消息结构体中正文内容的大小
	msgflg:		发送消息方式
		0			以阻塞方式发送消息
        IPC_NOWAIT	以非阻塞方式发送消息

返回值:
	成功返回 0, 失败返回 -1;

用法:
	#define LEN	(sizeof(MSG)-sizeof(long))

	/* 发送消息相关结构体 */
	typedef struct msgbuf
    {
        long mtype;     // type   > 0
        char mtext[64]; // message data
    }MSG;
	
	MSG msg;
	char buf[64] = {0};//缓冲区
	fgets(buf,64,stdin);
	msg.mtype = 100;
	strcpy(msg.mtext, buf);	

	msgsnd(msgid, &msg, LEN, 0);

3.接收消息 – msgrcv()

#include 
#include 
#include 

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数:
	msgid:		消息队列id
	msgp:		接收消息相关的结构体的首地址
	msgsz:		消息结构体中正文内容的大小
 	msgtyp:		指定接收类型
	msgflg:		接收消息方式
   		0			以阻塞方式接收消息
     	IPC_NOWAIT	以非阻塞方式接收消息

   返回值:
	成功返回 0, 失败返回 -1;

4.操作消息队列 – msgctl()

#include 
#include 
#include 
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数:
	msgid:	消息队列id号
	cmd:	操作消息队列的命令
    IPC_STAT:	获取消息队列的信息,需要用到第三个参数
		IPC_SET:	设置消息队列信息
   		IPC_RMID:	删除消息队列,第三个参数填NULL;
返回值:
	成功返回 0,失败返回 -1
3)信号灯集
信号量使用流程:

1.创建信号灯集 – semget()

#include 
#include 
#include 

int semget(key_t key, int nsems, int semflg);
参数:
	key:	产生信号灯id需要使用的键值,ftok()或者IPC_PRIVATE获取
	nsems:	信号灯集中包含信号的个数
	semflg:	标志位,信号灯的访问权限。如,创建需要添加 IPC_CREAT
返回值:
	成功返回信号灯集ID号,错误返回 -1

2.操作信号灯(查/改/删) – semctl()

#include 
#include 
#include 
int semctl(int semid, int semnum, int cmd, ...);
参数:
	semid:	信号灯集id
    semnum:	要控制的信号灯编号(编号从零开始)
    cmd:	控制命令
        IPC_RMID	删除信号灯集合
        IPC_STAT	查看信息
		IPC_SET		设置初始化属性
        GETVAL	获取信号灯的值
    参数4:   对于cmd参数的补充
        cmd为GETVAL时,返回值为信号量的当前值
        cmd为IPC_RMID时,为空
        cmd为IPC_SET时,传入共用体(需要自行定义)
        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) */
           };

返回值:
	失败返回 -1

3.实现P、V操作 – semop()

#include 
#include 
#include 

int semop(int semid, struct sembuf *sops, size_t nsops);
参数:
	semid:	信号灯集id
   	sops:	传递结构体数组的首地址,或者一个结构体的地址
        /* 该结构体已在头文件中定义 */
        struct sembuf
        {
            unsigned short sem_num;  /* 要操作的信号灯编号 */
            short          sem_op;   /* 0 	等待信号灯的值变为0
                                        1 	V操作
                                        -1	P操作
                                     */
            short          sem_flg;  /* 通常为0,表示阻塞等待 
            							IPC_NOWAIT
            							IPC_UNDO
            						 */
        }

	nsops:	表示需要控制多少个信号灯
返回值:
	成功返回0,失败或其他返回 -1

进程间通讯方式比较

pipe: 具有亲缘关系的进程间,半双工,数据在内存

fifo: 可用于任意进程间,双工,有文件名,数据在内存

signal: 唯一的异步通信方式

msg: 常用于cs模式中,按消息类型访问,可有优先级

shm: 效率最高(直接访问内存),需要同步、互斥机制

sem: 配合共享内存使用,用以实现同步和互斥

你可能感兴趣的