常见的通信方式: 单工 半双工 全双工

易用性: 消息队列 > Unix Domain Socket > 管道 > 共享存储
效率: 共享存储 > Unix Domain Socket > 管道 > 消息队列
常用: 共享存储 和 Unix Domain Socket

1.本地进程间通信

1.1管道 [无名管道]

有血缘关系
半双工通信

它可以看作是一种特殊的文件,但它不是普通的文件,它不属于任何文件系统,只存在于内存中.
它由两个文件描述符引用,一个表示读端,一个表示写端.
对于它的读写可以使用普通的read/write函数.

1.管道本质是一块内核缓冲区,内部使用环形队列实现.缓冲区默认大小4K,使用ulimit -a查看. 实际操作过程中,缓冲区会根据数据压力做适当调整.
2.规定数据从管道的写端流入管道,从读端流出管道. 即数据只能单向流动,若要实现双向流动,必须使用两个管道.
3.当两个进程都退出的时候,管道也自动消失.
4.管道的读端和写端默认阻塞.
5.管道中的数据被读取后便不复存在.
6.管道只能用在有血缘关系的进程间通信.

描述:
原型:
#include
int pipe(int pipefd[2]);

返回值:
成功,返回0
失败,返回-1,设置errno值.

参数:
fd[0]存放管道的读端
fd[1]存放管道的写端

读写行为(阻塞模式情况)
读操作 有则读之,无则阻之
-有数据(不关心写端是否关闭或退出),则读操作正常返回
-无数据
-若写端全部关闭,则读操作直接返回.返回值=0
-若写端部分关闭或都未关闭,则读操作阻塞
写操作 空则写之,满则阻之
-读端全部关闭.则管道’破裂’,内核给调用写操作的进程发送SIGPIPE信号.
-读端部分关闭或都未关闭
-缓冲区满,则写操作阻塞
-缓冲区有空间,则写操作不阻塞.

设置管道非阻塞
管道的读端和写端默认阻塞,通过fcntl设置非阻塞.
若读端设置非阻塞
-写端未关闭且管道无数据,则返回-1
-写端未关闭且管道有数据,则返回实际读取字节数
-写端已关闭且管道无数据,则返回0
-写端已关闭且管道有数据,则返回实际读取字节数

设置非阻塞 int flags = fcntl(fd[0], F_GETFL, 0); flags |= O_NONBLOCK; fcntl(fd[0], F_SETFL, flags);

查看管道缓冲区大小

1.ulimit -a
图片.png
2.fpathconf函数
long fpathconf(int fd, int name);
例如
printf(“size=[%ld]\n”, fpathconf(fd[0], _PC_PIPE_BUF));
printf(“size=[%ld]\n”, fpathconf(fd[1], _PC_PIPE_BUF));

man 2 pipe

f[0]和f[1]对应进程的文件描述符,如下图图片.png

父子进程间通信

父子进程间通信 [ 代码 ]

兄弟进程间通信

1.2FIFO [有名管道]

无血缘关系
半双工通信
先进先出

管道本质是一块内核缓冲区.

创建管道
1.命令方式
格式: mkfifo 管道名
2.函数方式
int mkfifo(const char *pathname, mode_t mode);

man 3 mkfifo

创建FIFO之后,可以使用open打开它,常见的文件IO函数都可用于FIFO,如close,read,write等.
但不支持lseek()操作.

描述:
原型:
#include
#include
int mkfifo(const char *pathname, mode_t mode);

返回值:
成功,返回0
失败,返回-1,设置errno值

参数:

写端 [ 代码 ]
读端 [ 代码 ]

1.3消息队列

无血缘关系
全双工通信

1.4共享存储

由一个进程创建,多个进程可以共享的内存段. 需要结合信号量来同步对共享内存的访问

mmap()本质是一种进程虚拟内存的映射方法,可以将一个文件,一段物理内存或者其它任意来源的数据对象映射到进程的虚拟内存地址空间.

infuq-zone\重要-网络与IO\mmap的应用.png
mmap的应用.png

描述:
原型:
#include
void mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset);

返回值:
成功,返回映射区首地址
失败,返回MAP_FAILED宏

参数:
addr: 指定映射起始地址,通常设为NULL,由系统指定
length: 指定length长度文件内容映射到内存, 不能为0, 通常为文件大小
prot: 映射区保护方式
读: PROT_READ 映射区必须要有读权限
写: PROT_WRITE
读写: PROT_READ | PROT_WRITE
flags: 映射区特性
MAP_SHARED: 写入映射区(前提具有写权限)的数据会写回磁盘文件,且允许其他映射该文件的进程共享.
MAP_PRIVATE: 对映射区的写入操作会产生一个映射区的复制(COW),修改操作不会写回磁盘文件.
fd: 由open函数返回的文件描述符,代表待映射的文件
offset: 文件指针偏移量,必须是4K的整数倍,通常为0,表示从文件头部开始映射.

man 2 mmap

描述: 释放mmap函数建立的映射区
原型:
#include
int munmap(void *addr, size_t length);

返回值:
成功,返回0
失败,返回-1,设置errno值

参数:
addr: 调用mmap函数返回的映射区首地址
length: mmap函数第二个参数

man 2 munmap

注意事项
1.创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区.
2.当MAP_SHARED时,必须映射区的权限<=文件打开的权限.
3.映射区的释放与文件是否关闭无关,只要映射建立成功,文件可以立即关闭.
4.当映射文件大小为0时,不能创建映射区.即对于映射的文件必须要有实际大小.
5.munmap函数传入的地址必须是mmap函数返回的地址.坚决杜绝指针++操作.
6.文件偏移量必须是0或者4K整数倍.
7.mmap函数创建映射区出错概率非常高,因此一定要检查返回值,确保映射区建立成功再进行后续操作.
8.mmap只是在虚拟内存分配了地址空间,并没有将文件内容加载到物理页.只有在第一次访问虚拟内存对应的页时,没有对应的物理页,发生缺页异常,才分配物理内存(即物理页),并将文件内容以页为单位(4096)加载到物理内存.

有血缘关系进程间通信

基于文件的映射 [ 代码 ]
匿名映射 [ 代码 ]

匿名映射 mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0)

无血缘关系进程间通信

基于文件的映射 [ 代码 ]


mmap读写文件 [代码]

infuq-zone\重要-网络与IO\mmap读写文件.png
mmap读写文件 [图片]

1.5信号

1.5.1信号机制

异步模式
每个进程收到的所有信号,都是由内核负责发送.

man 7 signal

信号状态

信号有三种状态: 产生,未决和递达

产生
-按键产生,如CTRL+C,CTRL+Z,CTRL+\
-系统调用产生,如kill,raise,abort
-软件条件产生,如定时器
-硬件异常产生,如非法访问内存(段错误),除0(浮点数例外),内存对齐出错(总线错误)
-命令产生,如kill命令

未决: 产生和递达之间的状态,主要由于阻塞(屏蔽)导致该状态. 信号未被处理.

递达: 递送并且到达进程. 信号已被处理.

信号处理方式

执行默认动作
忽略信号(丢弃不处理)
捕捉信号(调用用户自定义处理函数)

信号默认动作包括: Term,Ign,Core,Stop,Cont
图片.png

The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

常用信号
SIGINT,SIGQUIT,SIGKILL,SIGSEGV,SIGUSR1,SIGUSR2,SIGPIPE,SIGALRM,SIGTERM,SIGCHLD,SIGSTOP,SIGCONT.

信号特质

信号的实现手段导致信号有很强的延时性,但对于用户来说,时间非常短,不易察觉.
Linux内核的进程控制块PCB是一个结构体(task_struct),除了包含进程ID,文件描述符表等,还包含信号相关信息,主要指:阻塞信号集和未决信号集.

阻塞信号集与未决信号集

阻塞信号集指当前进程要阻塞的信号的集合.
未决信号集指当前进程中还处于未决状态的信号的集合.

1.5.2信号相关函数

signal函数

描述:注册信号捕捉函数
原型:
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

参数:
signum: 信号编号
handler: 信号处理函数

man 2 signal

建议使用sigaction.

样例代码 [ 代码 ]

sigaction函数

描述:注册信号捕捉函数
原型:
#include
int sigaction(int signum, const struct sigaction act, struct sigaction oldact);

返回值:

参数:
signum: 捕捉的信号
act: 传入参数,新的处理方式
oldact: 传出参数, 旧的处理方式

kill函数(命令)

描述: 向进程发送指定信号
原型:
#include
#include
int kill(pid_t pid, int sig);
返回值:
成功返回0
失败返回-1,设置errno值
参数:
pid参数:
若>0,则表示信号被发送给指定的进程.
若=0,则表示信号被发送给与调用kill函数的进程(当前进程)属于同一进程组的每个进程.
若<-1,则表示信号被发送给同一进程组(进程组ID=abs(pid))的每个进程.
若=-1,则表示信号被发送给当前进程有权限向其发送信号的每个进程,除了进程1(init).
sig: 信号值.

进程组: 每个进程都归属一个进程组,进程组是一个或多个进程集合.它们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同.

man 2 kill

命令:
kill -SIGKILL PID

man 1 kill

使用kill -l命令可以查看当前系统有哪些信号,不存在0号信号.
1-31号的信号称为常规信号(也叫普通信号/标准信号)
34-64号的信号称为实时信号,驱动编程与硬件相关.

sigqueue函数

描述: 向进程发送指定信号

abort函数 raise函数

描述: 给当前进程发送指定信号.
原型:
#include
int raise(int sig);

返回值:
成功返回0
失败返回非0
参数:
sig: 信号值.

raise(sig) == kill(getpid(), sig)

man 3 raise

描述: 给当前进程发送异常终止信号(SIGABRT),并产生core文件.
原型:
#include
void abort(void);

返回值:
参数:

abort() == kill(getpid(), SIGABRT)

alarm函数

描述: 设置定时器(闹钟).在指定seconds后,内核会给当前进程发送SIGALRM信号.进程收到该信号,默认动作终止.每个进程有且只有一个定时器.
原型:
#include
unsigned int alarm(unsigned int seconds);
返回值:
返回原定时器剩余秒数.
参数:

取消定时器: alarm(0)

样例代码 [ 代码 ]

setitimer函数

描述: 设置定时器,可替代alarm函数,精度微秒(us),可以实现周期定时.
原型:
#include
int getitimer(int which, struct itimerval curr_value);
int setitimer(int which, const struct itimerval
new_value, struct itimerval *old_value);

返回值:
成功返回0
失败返回-1,设置errno值.

参数:
which: 指定定时方式
-自然定时: 计算自然时间. 取值ITIMER_REAL.产生信号SIGALRM
-虚拟空间计时(用户空间): 只计算进程占用CPU的时间. 取值ITIMER_VIRTUAL.产生信号SIGVTALRM
-运行时计时(用户+内核): 计算占用CPU以及执行系统调用的时间. 取值ITIMER_PROF.产生信号SIGPROF
new_value: struct itimerval
itimerval.it_value: 触发时间
itimerval.it_interval: 触发周期
old_value: 传出参数. 存放原timeout值,一般指定为NULL.

1.5.3信号集相关函数

sigemptyset函数

描述: 将信号集清零
原型:
int sigemptyset(sigset_t *set);
返回值:
成功返回0
失败返回-1,设置errno值

sigfillset函数

描述: 将信号集置1
原型:
int sigfillset(sigset_t *set);
返回值:
成功返回0
失败返回-1,设置errno值

sigaddset函数

描述: 将某个信号加入信号集合
原型:
int sigaddset(sigset_t *set, int signum);
返回值:
成功返回0
失败返回-1,设置errno值

sigdelset函数

描述: 将某个信号移除信号集合
原型:
int sigdelset(sigset_t *set, int signum);
返回值:
成功返回0
失败返回-1,设置errno值

sigismember函数

描述: 判断某个信号是否在信号集合中
原型:
int sigismember(const sigset_t *set, int signum);
返回值:
存在返回1
不在返回0
失败返回-1,设置errno值

sigprocmask函数

描述: 用来屏蔽信号或解除屏蔽.
其本质读取或修改进程控制块中的信号屏蔽字(阻塞信号集)
屏蔽信号只是将信号处理延后执行(延至解除屏蔽),而忽略信号表示将信号丢弃不处理.
原型:
int sigprocmask(int how, const sigset_t set, sigset_t oldset);
返回值:
成功,返回0
失败,返回-1,设置errno值
参数:
how: 假设当前信号屏蔽字为mask
-SIG_BLOCK:此值被设置,则set表示需要屏蔽的信号.相当于mask=mask|set.
-SIG_UNBLOCK:此值被设置,则set表示需要解除屏蔽的信号.相当于mask=mask&~set.
-SIG_SETMASK:此值被设置,则set表示用于替代原始屏蔽集的新屏蔽集.相当于mask=set. 若解除了对当前若干个信号的屏蔽,则在方法调用返回前,至少将其中一个信号递达.
set:待屏蔽或解除的信号集.
oldset:传出参数,保存旧的信号屏蔽字.

屏蔽CTRL+C [代码]

sigpending函数

描述: 读取当前进程的未决信号集
原型:
int sigpending(sigset_t *set);
返回值:
成功,返回0
失败,返回-1,设置errno值
参数:
set: 属于传出参数

1.5.5SIGCHLD信号

1.6UNIX Domain Socket [无名管道]

无血缘关系
全双工通信

socket(AF_UNIX, SOCK_STREAM, 0)
socket(AF_UNIX, SOCK_DGRAM, 0)

服务端 [ 代码 ]
客户端 [ 代码 ]

并不会向文件中写入数据

1.7其他函数

socketpair [无名管道]

有血缘关系
全双工通信

include
#include
int socketpair(int domain, int type, int protocol, int sv[2]);

例如
socketpair(AF_UNIX, SOCK_STREAM, 0, [9, 10]);

2.网络进程间通信