System V IPC和POSIX
让我们通过一个简单的餐馆订单处理的比喻来理解System V IPC和POSIX消息队列及它们之间的区别。
假设一个餐馆有前厨和后厨,前厨负责接收顾客的订单,然后需要将订单传递给后厨以便烹饪。
System V IPC 消息队列
System V IPC相当于使用一种特殊的订单系统。在这个系统中:
- 前厨写下订单并给每个订单一个指定的类型标签,比如“前菜”,“主菜”,“甜点”等。
- 后厨有一个列表,里面有所有的订单类型。
后厨可以选择查看特定类型的订单,比如说现在后厨只想看“主菜”的订单。 - 这些订单被放在一个特殊的柜台上,后厨工作人员必须按照特定的规则来检查柜台,比如使用特殊的钥匙。(这里的“钥匙”指的是System V IPC消息队列中的键值 key_t)
- 订单一旦被写下,即便前厨的工作人员离开了,订单也会留在柜台上,直到后厨的人显式地处理它。
POSIX 消息队列
而如果这家餐馆使用POSIX消息队列来处理订单:
- 前厨使用一个更现代的系统来写订单,每个订单都可以标记一个紧急级别。
- 后厨有一个显示器,它会自动将最紧急的订单排在最前面。
- 后厨可以一直查看这个显示器,并按紧急程度的先后顺序取出订单。
- 显示器还有一个灯光提示,如果后厨太忙没时间看,灯光会告诉他们是否有新的订单等待处理。(这里的“灯光提示”可以想象成 POSIX 消息队列的非阻塞特性)
- 一旦某个订单完成,显示器自动更新,不需要特殊的钥匙或操作。
区别
现在来总结一下它们之间的主要区别:
- 名称和访问方式:SysV IPC 使用一个整数键值(key_t)来访问消息队列,这需要一种特殊的方法来生成和获取键值。而POSIX消息队列像文件一样,通过一个字符串名字来访问,这就使得它看起来更像是操作系统的一部分而不是一个单独的服务。
- 消息优先级:POSIX允许对消息进行优先级排序,这样可以确保最重要的消息首先被处理。SysV的消息队列也可以实现类似的功能,但通常不那么直观或简单。
- 接口和功能:POSIX API通常被认为更现代、更一致,有更好的错误处理和更完整的功能集合。SysV IPC在某些操作系统的某些版本中可能不那么直观或者不那么可靠。
这些区别使得在新的系统或者需要更多功能的场景下,POSIX消息队列可能是更好的选择,而SysV IPC可能在旧系统中因为兼容性而被使用。
IPC Namespace
想象一下,进程间通信(IPC)就像一个邮局系统。这个系统内,城市里的居民(各个进程)想要发送信件或包裹(数据)给对方。为了完成这项任务,他们会用到邮局提供的服务,比如信箱、包裹传送带、或者邮递员(这些对应于IPC的通信方式,如消息队列、信号量、共享内存等)。
现在,来到“ipcnamespace”。这个词可以被理解为独立的小社区或者小分区。每个小社区内,居民有自己的小邮局,他们自己的信箱和包裹传送带。社区外的人是不能直接使用别的社区的信箱和邮局服务的。这样一来,每个社区内的通信都是独立和私密的。
在计算机中,IPC namespaces起到了类似的作用。当你创建了一个新的IPC namespace后,你实际上是在现有的操作系统中创建了一个隔离的小社区,进程可以在这个小区内自由地传递信息,而外面的进程则不能干预。这在你不想让某些程序受到外界影响的时候特别有用,比如在使用Docker这样的容器技术来隔离和管理不同应用程序时。每个容器都运行在它自己的IPC namespace中,确保了内部进程通信的独立性和安全性。这就像给应用程序提供了一个“私人邮局”,只服务于容器内部,互不影响。
cmd.Run()
cmd.Run()方法在命令成功执行完成时返回nil(没有错误),如果有错误发生(如命令找不到、执行时出错),则返回error类型的值。
啥是消息队列
消息队列是一种进程间通信(IPC)或应用程序间通信(介于不同应用程序或服务之间)的方法,它允许进程通过读/写入队列进行通信,而无需直接相互知晓。队列是消息的有序集合,这些消息可以是数据、命令或者任何可以转换为字节串的信息。
消息队列的工作原理类似于现实生活中的邮局系统:
-
发送消息(发送信件): 一个进程(或线程)作为消息的发送者(像人们发送信件一样),将消息放入到一个消息队列中。
-
存储消息(邮局保管信件): 队列临时存储这些信息,直到接收者准备好接收它们。消息队列能保证消息的顺序,并且确保消息不会丢失。
-
接收消息(收取信件): 另一个进程(或线程)作为消息的接收者,从队列中按顺序取出消息进行处理。
消息队列相关结构
消息队列的基本结构很简单,但至关重要,能够有效地协调消息的生产者(发送方)和消费者(接收方)。以下是消息队列的一些主要组成部分:
-
消息: 消息是消息队列结构中基本的数据单位。它可以包含任意形式的数据,如字符串、对象或字节流。通常,消息由两部分组成:消息头(包含元数据,如消息ID、时间戳、优先级等)和消息体(包含实际发送的数据)。
-
队列: 队列是一个有序的消息集合,它按照先入先出(FIFO)的原则操作。也就是说,最先发送到队列的消息将是最先被接收处理的。队列在消息的生产者和消费者之间提供了一个缓冲区,允许他们在不同的速率下工作。
-
生产者(发送方): 生产者是创建和发送消息至队列的实体。生产者将消息发送到队列,并且一旦发送完成,它通常不再关心消息是否被消费。
-
消费者(接收方): 消费者从队列中读取和处理消息。消费者可以是直接监听队列的进程,当消息到达时立即处理,也可以是定期检查新消息的进程。在某些情况下,消费者处理完消息后会发送一个响应或者结果。
ipcs -q
ipcs命令是Linux操作系统中用于报告Inter-Process Communication (IPC)设施的状态的一个工具。ipc代表进程间通信,它可以包括消息队列、信号量和共享内存段等。ipcs命令能够显示每种类型IPC对象的当前状态,这对于诊断或系统监控很有用。
当ipcs命令与-q选项一起使用时(即ipcs -q),它将专门报告关于消息队列的信息。输出将包括每个消息队列的key、msqid以及其他附加信息。
这里是一个ipcs -q命令的典型输出示例:
------ Message Queues --------
key msqid owner perms used-bytes messages
0x12345678 0 myuser 644 0 0
0xaabbccdd 1 anotherusr 644 16384 4
解释一下以上示例中每一列代表的意义:
- key:用于唯一标识消息队列的键值。
- msqid:消息队列的内核内部唯一标识符。
- owner:创建消息队列的用户。
- perms:消息队列的权限,通常以八进制形式显示。
- used-bytes:当前在队列中的所有消息占用的总字节数。
- messages:当前在队列中的消息数量。
ipcmk -Q
ipcmk是Linux下用来创建IPC(Inter-Process Communication)资源的命令,它可以创建消息队列、共享内存以及信号量。当你使用ipcmk -Q(请注意,参数Q是大写的),它将创建一个新的消息队列。
这里是ipcmk -Q的简单用法及输出:
$ ipcmk -Q
Message queue id: 32768
在这个例子中,ipcmk -Q命令创建了一个新的消息队列,并输出它的消息队列ID(msqid)。这个ID是用来访问和管理消息队列的关键标识。如果需要在你的程序中使用这个消息队列,你将需要这个ID。
msqid 和key的区别
想象一下我们有一个邮局,邮局里有许多邮箱,每个邮箱都有一个独一无二的邮箱号码(这就像是msqid)。如果你想放一封信进某个邮箱,你就需要知道那个邮箱的号码。
但是,要得到一个邮箱号码,你需要先告诉邮局一个密码或者代码,这样他们就能根据这个密码分配一个邮箱给你(这个密码就像是key)。这个密码有可能是共享的,也就是说,其他人也可能知道这个密码,并用它来请求同样的邮箱号码。
比方说,一个学校想要设立一个信息发布系统,他们决定使用一个消息队列来发布通知,并且他们选择使用学校的识别码“123”和一个特定的项目标识符’A’生成这个key。
首先,他们为此生成一个key:
// 学校使用识别码(例如123)和项目标识符(例如 'A')生成key
key_t key = ftok("/path/to/somefile", 'A');
得到这个key后,学校可以用它来获取它们自己的邮箱号码(msqid):
// 学校通过提供key来请求(如果不存在,则创建)一个邮箱号码(msqid)
int msqid = msgget(key, IPC_CREAT | 0666);
如果请求成功,邮局(操作系统)会分配一个独一无二的邮箱号码(msqid)给学校的信息发布系统。
这时,学校的信息发布部门就可以通过这个邮箱号码(msqid)来发送通知,而其他学校的部门或者服务如果知道这个邮箱号码,也可以到"邮局"拿这个邮箱号码来接收通知。他们不需要知道最开始的密码(key);只要他们有邮箱号码(msqid),他们就可以访问那个邮箱(消息队列)。
代码
相比之前只是Cloneflags:CLONE_NEWUTS|CLONE_NEWIPC不同而已
package main
import
(
"os/exec"
"syscall"
"os"
"log"
)
func main(){
cmd:=exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC ,
}
cmd.Stdin=os.Stdin
cmd.Stdout=os.Stdout
cmd.Stderr=os.Stderr
if err:=cmd.Run();err!=nil{
log.Fatal(err)
}
}