每个进程的用户空间都是独立的,不能相互访问。
所有进程的内核空间(32位系统3G-4G)都是共享的
应用场景
- 作为缓冲区,处理速度不同的进程之间的数据传输
- 资源共享:多个进程之间共享同样的资源,一个进程对共享数据的修改,别的进程应该立刻看到
- 数据传输: 一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
一、管道
缺点:缓存区在内核中大小受限,只能承载无格式字节流;单向通信方式,低效,不适合频繁交换数据
生命周期随着进程创建而建立,随着进程终止而消失
遵循先进先出原则,不支持 lseek 之类的文件定位操作
需要双方通信时,需要建立起两个管道
读端不读或者读的慢,写端要等待读端;
读端关闭,写端收到SIGPIPE信号直接终止;
写端不写或者写的慢,读端要等待写端;
写端关闭,读端读完pipe内部的数据然后再读,会读到0为止,表明读到文件结尾;
1.1 匿名
管道 pipe
特殊文件只存在于内存,没有存在于文件系统中
只能用于有亲缘关系的进程间
shell 命令中的竖线就是匿名管道
pipe() 和 fork() 搭配
1.2 有名
管道 fifo
mkfifo myfifo #在文件系统中创建一个类型为 p 的设备文件
int mkfifo(const char *pathname, mode_t mode); //默认在当前的路径下创建
mkfifo("myfifo", 0666);
open("myfifo", O_RDONLY);
open(MY_FIFO, O_WRONLY);
二、消息队列
是消息的链表,存放在内核中并由消息队列标识符标识,具有足够特权的任何进程都可以往一个队列放置/读出一个消息,不需要等待消息的到达。
- 生命周期是随内核的,需要显示删除。
- 消息结构相关联的类型字段(msg_type)提供了两个特性:
(1)标识消息,使得多个进程在单个队列上复用消息。
(2)用作优先级字段,允许接收者以不同于先进先出的某个顺序读出各个消息。
优点
灵活,消息体可以自由定义,支持多种数据类型和优先级。
可靠,消息持久化且不会因发送者崩溃而丢失。
高效,支持大量数据的传输和并行处理
缺点
:通信不及时; 需要访问权限;用户态与内核态之间的数据拷贝开销;有大小限制,不适合比较大数据的传输
在内核中一条消息最大长度MSGMAX 和一个队列的最大长度MSGMNB(以字节为单位)
- 2.1 System V 消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg); //发送消息
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg); //读取消息
//cmd参数:IPC_STAT获取信息、IPC_SET设置属性、IPC_RMID删除
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 2.2 POSIX 消息队列
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
mqd_t mq_open(const char *name, int oflag, ...); //打开一个已存在的消息队列,或创建一个新的消息队列
//发送消息
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);
//接收消息
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio);
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned int *msg_prio, const struct timespec *abs_timeout);
//设置/获取消息队列的属性
int mq_getattr(mqd_t mqdes, struct mq_attr *mqstat);
int mq_setattr(mqd_t mqdes, const struct mq_attr *mqstat, struct mq_attr *omqstat);
int mq_close(mqd_t mqdes); //关闭
int mq_unlink(const char *name); //删除一个命名的消息队列。
三、共享内存
– 最快的IPC
映射一段能被其它进程访问的内存,这段共享内存由一个进程创建,多个进程均能访问
缺点
:存在进程间共享资源竞争,常与信号量
配合使用
生命周期是随内核的,进程要主动删除其创建的共享内存:ipcrm -m {shmid}
共享内存块的大小都必须是系统页面大小的整数倍。系统页面大小指的是系统中单个内存页面包含的字节数。在 Linux 系统中,内存页面大小是4KB,不过您仍然应该通过调用 getpagesize 获取这个值(通过man 2 getpagesize查看 )
- 3.1 System V 共享内存
#include <sys/ipc.h>
#include <sys/shm.h> // shmget
#include <sys/types.h> // key_t
// key_t ftok(const char *pathname, int proj_id); //获取key值
// int shmget(key_t key, size_t size, int shmflg); //得到共享内存标识符,不存在时创建并返回
// void* shmat(int shmid, const void *shmaddr, int shmflg); //共享内存绑定到当前进程的地址空间中
// int shmdt(const void *shmaddr); //取消共享内存与进程地址空间之间的关联
// int shmctl(int shmid, int cmd, struct shmid_ds *buf); //cmd表示具体控制动作:IPC_STAT,IPC_SET改变状态,IPC_RMID 删除
key_t key = ftok(PATH_NAME, PROJ_ID);//获取key值
shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL); //操作系统给用户返回的id
// ipcs命令查看有关进程间通信设施的信息
// -q:列出消息队列相关信息。
// -m:列出共享内存相关信息。
// -s:列出信号量相关信息
- 3.2 POSIX 共享内存
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
int shm_open(const char *name, int oflag, mode_t mode); //创建或打开一个POSIX共享内存对象
int ftruncate(int fd, off_t length); //设置大小
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); //映射到进程的地址空间
int munmap(void *addr, size_t length); //解除映射
int shm_unlink(const char *name); //删除共享内存对象
四、信号量
一个整型的计数器表示资源的数量,是为了保证进程间的同步与互斥而设计的
缺点
信号量是通过标识符而不是文件描述符来引用,同时等待一个文件IO和文件描述符的输入之类的操作就会变得比较困难(不能使用I/O模型来同时检测)
两原子操作:
P 操作: 把信号量 -1。相减后信号量 >= 0,表明还有资源可使用,进程可正常继续执行,否则进程需阻塞等待
V 操作: 把信号量 +1。相减后信号量 > 0,表明当前没有阻塞中的进程;否则表明当前有阻塞中的进程,将该进程唤醒运行
- 4.1 System V 信号量(复杂且功能丰富)
System V信号量不仅限于二元状态可以取任何非负整数值, 资源计数
核心在于信号量集(semaphore set)允许在单个原子操作中管理多个信号量,这是一种强大的同步机制,可以用来解决复杂的协调任务。
银行家算法是一种预防死锁的方法,它确保分配资源不会导致系统进入不安全状态。在这个算法中,System V 信号量集用于同时管理多种资源类型
生产者和消费者进程共享一个固定大小的缓冲区,信号量集在这里用于同时控制对缓冲区的访问(互斥信号量)和跟踪缓冲区中项目的数量(计数信号量)
数据库管理系统中,多个事务可能同时操作多个数据项。这里,信号量集用于确保事务的原子性和一致性。每个数据项可以有一个关联的信号量,用于控制对该项的访问
多线程服务器应用中,信号量集可以用来管理线程池中的线程数量,确保服务器不会因为过多的并发请求而过载
#include <sys/types>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/stat.h>
// 每个信号量集都有一个关联的semid_dss结构体
struct semid_ds{
struct ipcperm sem_perm; //ownship and permissions
time_t sem_otime; //time of last semop()
time_t sem_ctime; //time of last changes
unsigned lone sem_nsems; //Number of semaphores;
};
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
};
int semget(key_t key,int nsems,int semflg); //创建一个新信号量集,或者获取一个既有集合的标识符
int semctl(int semid,int sennum, int cmd,.../*union semnu arg*/);//设置或获取信号量集的属性
struct sembuf{
unsinged short sem_num; //semaphore number
short sem_op; //operation to be performed
short sem_flag; //operation flags(IPC_NOWAIT and SEM_UNDO)
};
int semop(int semid,struct sembuf *sops,unsigned int nsops);//对一个或多个信号量执行原子操作
- 4.2 POSIX
二元信号量
更轻量级, 适用于不复杂操作的对性能敏感的基本同步任务
有名信号量
: 操作系统内核维护的具有全局唯一名字的信号量
无名信号量
: 是一种不具有全局唯一名字的信号量,通常只能在相关的进程或线程之间进行共享
二进制信号量
: 只有0和1两种取值的信号量
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); //创建或打开有名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化无名信号量
int sem_getvalue(sem_t *sem, int *sval); //获取当前信号量的值
int sem_post(sem_t *sem); //V操作:信号量 +1
int sem_wait(sem_t *sem); //等待信号量减小。如果信号量的值大于0,则将其减小1并立即返回,否则会阻塞当前线程直到信号量变为大于0为止
int sem_trywait(sem_t *sem); //尝试等待信号量减小的非阻塞版本。如果信号量的值大于0,则将其减小1并立即返回,否则会立即返回,并且不会阻塞当前线程
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //函数允许设置一个超时时间,如果在指定的时间内未能获得信号量,函数将返回一个特定的错误码
int sem_close(sem_t *sem); //关闭有名信号量
int sem_destroy(sem_t *sem); //销毁无名信号量
int sem_unlink(const char *name); //从系统中删除有名信号量
五、信号
缺点
:传递信息少
用于通知接收进程某个事件已经发生,唯一的异步通信机制。
信号事件来源主要: 硬件来源(如键盘 Cltr+C )和 软件来源(如 kill 命令)
可以在任何时候发送信号给某一进程,一旦有信号产生,进程就对信号处理(执行默认操作,定义一个信号处理函数,忽略信号)
# Ctrl+C 产生 SIGINT 信号,表示终止该进程;
# SIGTERM 终止该进程;通过 Core Dump 将当前进程的运行状态保存在文件里面,方便事后分析问题在哪里
# Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束;
kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
六、套接字
3个属性:域、类型、协议。唯一的跨网络主机的通信机制
// domain: AF_INET 用于 IPV4、
// AF_INET6 用于 IPV6
// AF_LOCAL/AF_UNIX 二者等价,用于本机
// type: SOCK_STREAM 字节流,对应 TCP
// SOCK_DGRAM 数据报,对应 UDP
// SOCK_RAW 原始套接字
int socket(int domain, int type, int protocal);
TCP 服务端负责监听的 socket 叫作监听 socket ==> listen_fd
调用 accept 时,连接成功,会返回一个已完成连接的 socket,叫作已完成连接 socket ==> sock_fd,用来后续传输数据。
是「两个」 不同 socket
UDP 是没有连接的,所以不需要三次握手。不存在客户端和服务端的概念
每次通信时,调用 sendto 和 recvfrom,都要传入目标主机的 IP 地址和端口
本地 socket 的实现效率大大高于 IPv4 和 IPv6 的字节流、数据报 socket 实现
不像 TCP 和 UDP 要绑定 IP 地址和端口,而是绑定一个本地文件
关于 System V 、POSIX 区别
参考:https://www.cnblogs.com/tongh/p/18002994
Linux
内核v2.6.6
和glibc2.3.4
之后的版本支持POSIX消息队列 (查看glibc版本:ldd --version
)
POSIX:可移植操作系统接口,是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称,其正式称呼为IEEE 1003,而国际标准名称为ISO/IEC 9945
标准线上地址:注册后可以在线阅读或者下载。
IEEE和Open Group 的POSIX认证
相关页面
20世纪80年代中期,Unix厂商试图通过加入新的、往往不兼容的特性来使它们的程序与众不同。局面非常混乱,麻烦也就随之而来了。为了提高兼容性和应用程序的可移植性,阻止这种趋势, IEEE(电气和电子工程师协会)开始努力标准化Unix的开发,后来由 Richard Stallman命名为“Posix”。
这套标准涵盖了很多方面,比如Unix系统调用的C语言接口、shell程序和工具、线程及网络编程
有了这个规范,就可以调用通用的API