IPC,进程间通信,是打破地址空间隔离的必经之路。本文按照个人理解对于IPC进行了一些分类与整理。
目录
Fork Based
匿名管道/匿名共享内存/互斥量
Path Based
命名管道/命名共享内存/消息队列/信号量/文件锁
PID Based
信号
Socket Based(IP:Port versus FilePath)
internet socket(with loopback)/unix domain socket
IPC
进程通过独占地址空间实现了隔离,然而,某些时候,我们希望进程之间协作。
- 模块化: 数据库单独在一个进程中,可以被复用
- 加速计算: 不同进程专注于特定的计算任务,性能更好
- 信息共享: 直接共享已经计算好的数据,避免重复计算
两个(或多个)不同的进程,通过内核或其他共享资源进行通信,来传递控制信息或数据。
Fork Based
本节介绍仅能在fork的父子进程间进行通信的IPC机制。
匿名管道
常见于shell,fd[1]用于写入数据,fd[0]用于读出数据。in-memory的pipe文件系统
代码语言:javascript复制int pipe(int fd[2]);
匿名共享内存
在mmap时通过匿名flag指定。
代码语言:javascript复制mmap (NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
互斥量
设置pshared为PTHREAD_PROCESS_SHARED使得其在进程间共享(默认PRIVATE)
代码语言:javascript复制int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr , int pshared);
Path Based
本节介绍可以通过文件路径名指定的IPC机制
命名管道
在pipe的基础上增加了路径名,使得外部可见。in-memory的pipe文件系统
代码语言:javascript复制int mkfifo(const char * pathname, mode_t mode)
命名共享内存
mmap时通过指定具名文件的fd,使得其外部可见
代码语言:javascript复制mmap (NULL, BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0))
shmget利用ftok映射文件名,使得其外部可见
代码语言:javascript复制key_t ftok( const char * fname, int id )
shmget(key_t key, size_t size, int shmflg)
消息队列
send msgp的第一个成员是type,在rcv时指定msgtyp,其他成员为消息体。
msgget利用ftok映射文件名,使得其外部可见
代码语言:javascript复制key_t ftok( const char * fname, int id )
int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
信号量
常作为进程间同步机制配合共享内存,semop加减信号量。
当进程需要减信号量而无法获取时,可以排队等待其他进程加信号量之后唤醒。
semget利用ftok映射文件名,使得其外部可见
代码语言:javascript复制 int semid = semget(key_t key, int nsems, int semflg)
int semctl(int semid, int semnum, int cmd, ...)
int semop(int semid, struct sembuf *sops, unsigned nsops);
文件锁
FFS中引入的文件系统原子性原语。 lock.l_type为F_UNLCK时解锁,否则加锁。
cmd以F_SETLK非阻塞获取锁,F_SETLKW阻塞
代码语言:javascript复制int fcntl(int fd,int cmd, struct flock*);
PID Based
本节介绍直接指定PID的IPC机制
信号
最基本的就是kill发信号,signal处理信号
代码语言:javascript复制typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
int kill(pid_t pid, int sig);
然后用掩码来屏蔽/接收信号。其中how对sigset执行以下操作
- SIG_BLOCK (union)
- SIG_UNBLOCK (intersection)
- SIG_SETMASK (equality)
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
int sigprocmask(int how, const sigset_t *set,sigset_t *oset);
为了防止unblock和pause之间插入信号,导致信号丢失,pause无法唤醒,引入sigsuspend实现原子性。
- 修改为新的sigmask
- pause
- 恢复原本的sigmask
int sigsuspend(const sigset_t *sigmask);
Socket Based
本节介绍通过socket进行的IPC机制
internet socket(with loopback)
经典的socket、bind、listen/accept、read/write、close通信。
socket的好处在于可以进行远程通信,而本地通信时进行loopback不走网卡。
这是因为在网络层,通过查询路由表,系统已经发现了目标地址就是本机,所以直接消费而不进行转发,因此不进入链路层。
代码语言:javascript复制int socket(int family, int type, int protocol);//family = AF_INET
在bind时其地址为ip:port
代码语言:javascript复制struct sockaddr_in
{
short int sin_family; /* AF_INET */
unsigned short int sin_port; /* port number */
struct in_addr sin_addr; /* internet address */
}
unix domain socket
internet socket需要走网络协议栈,所以提供了很多无必要的开销例如checksum。
代码语言:javascript复制int socket(int family, int type, int protocol);//family = AF_UNIX
在bind时其地址为sockaddr_un,为文件路径名,而非ip:port,因此比loopback更快。本质上这类归到上面path based也可以。
代码语言:javascript复制struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* Pathname */
};
释放connection socket需要提供路径名,而data socket则和正常的socket同样用法
代码语言:javascript复制 unlink(SOCKET_NAME);
连接时需要提供路径名
代码语言:javascript复制 struct sockaddr_un addr;
int ret;
int data_socket;
data_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (data_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);
ret = connect (data_socket, (const struct sockaddr *) &addr,
sizeof(struct sockaddr_un));
if (ret == -1) {
fprintf(stderr, "The server is down.n");
exit(EXIT_FAILURE);
}
前沿
微内核在IPC方面做出了很大改进,例如
- LRPC(SOSP 89)
- seL4 IPC(SOSP 09)
- XPC(ISCA 19)
读起来太累了,把linux的写完完事儿。
Reference
- Linux Kernel - IPC
- SJTU,IPADS,OS-09-IPC
- SJTU,IPADS,CSP-12-Arch_fror_OS
- SOSP89-Lightweight remote procedure call
- ISCA19-XPC
- http://www.cs.um.edu.mt/~jcor1/SystemsProgramming/CourseMaterials/9_AdvancedSignalOperations.pdf
- https://www.man7.org/linux/man-pages/man7/unix.7.html