System|IPC|Rethinking IPC

2021-11-22 10:53:14 浏览数 (1)

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)
代码语言:javascript复制
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
代码语言:javascript复制
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

0 人点赞