共计 2268 个字符,预计需要花费 6 分钟才能阅读完成。
前提常识:每个过程都有本人的用户空间,而内核空间是每个过程共享的。因而过程之间想要进行通信,就须要通过内核来实现。
管道:
管道是最简略,效率最差的一种通信形式。
管道实质上就是内核中的一个缓存,当过程创立一个管道后,Linux 会返回两个文件描述符,一个是写入端的描述符,一个是输入端的描述符,能够通过这两个描述符往管道写入或者读取数据。
如果想要实现两个过程通过管道来通信,则须要让创立管道的过程 fork 子过程,这样子过程们就领有了父过程的文件描述符,这样子过程之间也就有了对同一管道的操作。
毛病:
半双工通信,一条管道只能一个过程写,一个过程读。
一个过程写完后,另一个过程能力读,反之同理。
音讯队列:
管道的通信形式效率是低下的,不适宜过程间频繁的替换数据。这个问题,音讯队列的通信形式就能够解决。A 过程往音讯队列写入数据后就能够失常返回,B 过程须要时再去读取就能够了,效率比拟高。
而且,数据会被分为一个一个的数据单元,称为音讯体,音讯发送方和接管方约定好消息体的数据类型,不像管道是无格局的字节流类型,这样的益处是能够边发送边接管,而不须要期待残缺的数据。
然而也有毛病,每个音讯体有一个最大长度的限度,并且队列所蕴含音讯体的总长度也是有下限的,这是其中一个不足之处。
另一个毛病是音讯队列通信过程中存在用户态和内核态之间的数据拷贝问题。过程往音讯队列写入数据时,会发送用户态拷贝数据到内核态的过程,同理读取数据时会产生从内核态到用户态拷贝数据的过程。
共享内存:
共享内存解决了音讯队列存在的内核态和用户态之间数据拷贝的问题。
古代操作系统对于内存治理采纳的是虚拟内存技术,也就是说每个过程都有本人的虚拟内存空间,虚拟内存映射到实在的物理内存。共享内存的机制就是,不同的过程拿出一块虚拟内存空间,映射到雷同的物理内存空间。这样一个过程写入的货色,另一个过程马上就可能看到,不须要进行拷贝。
(这里的物理内存貌似不是内核空间的内存?)
信号量:
当应用共享内存的通信形式,如果有多个过程同时往共享内存写入数据,有可能先写的过程的内容被其余过程笼罩了。
因而须要一种爱护机制,信号量实质上是一个整型的计数器,用于实现过程间的互斥和同步。
信号量代表着资源的数量,操作信号量的形式有两种:
P 操作:这个操作会将信号量减一,相减后信号量如果小于 0,则示意资源曾经被占用了,过程须要阻塞期待;如果大于等于 0,则阐明还有资源可用,过程能够失常执行。
V 操作:这个操作会将信号量加一,相加后信号量如果小于等于 0,则表明以后有过程阻塞,于是会将该过程唤醒;如果大于 0,则示意以后没有阻塞的过程。
(1)信号量实现互斥:
信号量初始化为 1
过程 A 在拜访共享内存前,先执行了 P 操作,因为信号量的初始值为 1,故在过程 A 执行 P 操作后信号量变为 0,示意共享资源可用,于是过程 A 就能够拜访共享内存。
若此时,过程 B 也想拜访共享内存,执行了 P 操作,后果信号质变为了 -1,这就意味着临界资源已被占用,因而过程 B 被阻塞。
直到过程 A 拜访完共享内存,才会执行 V 操作,使得信号量复原为 0,接着就会唤醒阻塞中的线程 B,使得过程 B 能够拜访共享内存,最初实现共享内存的拜访后,执行 V 操作,使信号量复原到初始值 1。
(2)信号量实现同步:
因为多线程下各线程的执行程序是无奈意料的,有些时候咱们心愿多个线程之间可能密切合作,这时候就须要思考线程的同步问题。
信号量初始化为 0
如果过程 B 比过程 A 先执行了,那么执行到 P 操作时,因为信号量初始值为 0,故信号量会变为 -1,示意过程 A 还没生产数据,于是过程 B 就阻塞期待;
接着,当过程 A 生产完数据后,执行了 V 操作,就会使得信号量变为 0,于是就会唤醒阻塞在 P 操作的过程 B;
最初,过程 B 被唤醒后,意味着过程 A 曾经生产了数据,于是过程 B 就能够失常读取数据了。
信号:
在 Linux 中,为了响应各种事件,提供了几十种信号,能够通过 kill - l 命令查看。
如果是运行在 shell 终端的过程,能够通过键盘组合键来给过程发送信号,例如应用 Ctrl+ C 产生 SIGINT 信号,示意终止过程。
如果是运行在后盾的过程,能够通过命令来给过程发送信号,例如应用 kill -9 PID 产生 SIGKILL 信号,示意立刻完结过程。
Socket:
后面提到的管道,音讯队列,共享内存,信号量和信号都是在同一台主机上进行过程间通信,如果想要跨网络和不同主机上的过程进行通信,则须要用到 socket。
实际上,Socket 不仅能够跨网络和不同主机进行过程间通信,还能够在同一主机进行过程间通信。
Socket 是操作系统提供给程序员操作网络的接口,依据底层不同的实现形式,通信形式也不同。
Socket 的零碎调用:
int socket(int domain, int type, int protocal)
针对 TCP 的 Socket 通信:
服务端和客户端初始化 Socket,失去文件描述符
服务端调用 bind,绑定 IP 和端口
服务端调用 listen,进行监听
服务端调用 accept,期待客户端连贯
客户端调用 connect,向服务端发动连贯申请。(TCP 三次握手)
服务端调用 accept 返回用于传输的 Socket 的文件描述符(和第一点失去的 Socket 不同)
客户端应用 write 写入数据,服务端调用 read 读取数据
客户端断开连接时会调用 close,服务端也会调用 close(TCP 四次挥手)
这里要留神的是,调用 accept,连贯胜利失去的 Socket 是用来传输数据的,而第一次初始化 Socket 是用来监听的,是两个不同作用的 Socket。
针对 UDP 的 Socket 通信: