进程间通讯
-
易用性:消息队列>域套接字>管道>共享内存(信号量一起使用)
-
效率:共享内存>域套接字>管道>消息队列
-
常用:共享内存、域套接字
1. 无名管道:
特点:只能用于具有亲缘关系的进程之间的通信(父子进程、兄弟进程)
单工通信。具有固定的读端和写端
创建时会返回两个文件描述符,分别用于读写管道
int pipe(int pipefd[2]);
pid[0]用于读、pid[1]用于写
一个进程只能使用一个(不能自己写自己读)
-
读管道:
管道中有数据,read返回实际读到的字节数
管道中无数据,写端全部被关闭,read返回0;写端没全被关闭,read堵塞等待。
-
写管道:
管道读端全被关闭,程序异常终止(也可以捕捉SIGIPE信号,是进程不终止)
管道读端没有全被关闭,管道已满,write堵塞;管道未满,write写入,返回实际写入的字节数
2. 有名管道
特点:可以使非亲缘的两个进程进行通信
通过路径名来操作,文件系统中可见,但内存存在内存中
文件IO来操作有名管道,但不支持leek操作
遵循先进先出的原则
-
int mkfifo(const char*path,mode t mode);mode 文件权限
-
open(const char*path,O_RDONLY)只读
-
open(const char*path,O_RDONLY|O_NONBLOCK)只读 非堵塞
-
open(const char*path,O_WRONLY)只写
-
open(const char*path,O_WRONLY|O_NONBLOCK)只写
不可以通过读写方式打开有名管道
以阻塞方式打开,如果只有一个进程以只读(只写)方式打开,则open堵塞,直到有一个进程以只写(只读)方式打开 一读一写
非堵塞方式则不会堵塞
如果要写入数据小于等于4K,要不全部写入,要不都不写入
3. 信号
信号是在软件层对中断机制的一种模拟,是一种异步通信方式
linux内核通过信号通知用户进程,不同信号代表不同的事件
进程对信号的响应方式:缺省方式、忽略方式、捕捉信号
信号产生
按键、系统调用函数产生、硬件异常、命令行产生(kill)、软件产生(被0除、访问非法内存)
-
SIGHUP:信号在用户终端关闭时产生,通常发给和该终端关联的会话内的所有进程
-
SIGINT:在用户键入INTR字符(ctrl-c)时产生。内核发送此信号到当前终端的所有前台进程
-
SIGQUIT:和SIGINT类似,但是由QUIT字符(ctrl-\)产生
-
SIGILL:在一个进程企图执行一个非法指令时产生
-
SIGSEV:在访问非法内存产生,如野指针、缓冲区溢出
-
SIGPIPE:当进程中往没有读端的管道中写入时产生,代表管道破裂
-
SIGKILL:用来结束进程,且不能被捕捉和忽略
-
SIGSTOP:用来暂停进程,且不能被捕捉和忽略
-
SIGTSTP:用来暂停进程,用户可以键入(ctrl-Z)发出
-
SIGCONT:让进程进入运行态
-
SIGALRM:用于通知进程定时器时间已到
-
SIGUSR1/2:保留给用户程序使用
-
SIGCHLD:子进程状态改变,发给父进程
shell命令:kill [-sig] pid kill -l killall
3.1 发送信号
int kill(pid,sig);
pid >0指定进程号;0代表同组进程;-1代表所有进程 ;<-1:取绝对值,发给绝对值所对应进程组的所有组成员
int raise(sig); 给自己发信号
3.2 定时器函数
一个进程只能设定一个定时器,时间到产生SIGALRM信号
unsigned int alarm(unsigned int seconds);
成功返回上个定时器返回时间,失败返回EOF;
useconds_t ualarm(useconds_t usecs, useconds_t interval);(循环发送)
在usecs微秒后,将SIGALRM信号发送给进程,并且之后每隔interval微秒再发送一次SIGALRM信号。
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
(定时发送alarm信号)
struct itimerval {
struct timeval it_interval; /* next value */ 周期触发
struct timeval it_value; /* current value */ 第一次触发};
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */};
-
which:间歇计时器类型,有三种选择
-
ITIMER_REAL //数值为0,计时器的值实时递减,发送的信号是SIGALRM。
-
ITIMER_VIRTUAL //数值为1,进程执行时递减计时器的值,发送的信号是SIGVTALRM。
-
ITIMER_PROF //数值为2,进程和系统执行时都递减计时器的值,发送的信号是SIGPROF。
-
-
value:负责设定timeout时间
-
ovalue:存放旧的timeout时间,通常用NULL
3.3 pause();
使调用进程(或线程)休眠,直到接受到终止进程的信号,或者接收到信号并从信号捕获函数中返回时,pause才返回
-
如果信号默认处理过程是终止进程,则进程终止,pause函数没有机会返回
-
如果信号默认处理过程是忽略,进程继续处于挂起状态,pause函数不返回
-
如果信号处理过程是捕捉,则调用完信号处理函数之后,pause返回-1
-
pause收到的信号如果被屏蔽,那么pause就不能被唤醒
int sigsuspend(const sigset_t *mask);
将进程的屏蔽字替换由mask给出的信号集,然后挂起进程
3.4 信号捕捉:
改变信号的行为
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
-
返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR
-
handler:信号处理函数:SIG_DFL代表缺省方式;SIG_IGN代表忽略信号
-
该函数由 ANSI 定义,由于历史原因在不同版本的 Unix 和不同版本的 Linux 中可能有不同的行为。因此应该尽量避免使用它,取而代之使用 sigaction 函数。
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
- act:传入参数,新的处理方式。
- oldact:传出参数,旧的处理方式。
struct sigaction{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);};
- sa_restorer:该元素是过时的,不应该使用,POSIX.1 标准将不指定该元素。(弃用)
- sa_sigaction:当 sa_flags 被指定为 SA_SIGINFO 标志时,使用该信号处理程序。(很少使用)
重点掌握:
① sa_handler:指定信号捕捉后的处理函数名(即注册函数)。
② sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
③ sa_flags:通常设置为 0,表使用默认属性;sa_flags :SA_RESTART:表示如果一个信号到来时进程正在执行某个系统调用(read/write等),执行完信号处理函数后返回系统调用,此系统调用会重新调用而不会出错。
3.5 SIGCHLD:
实现回收子进程
产生条件:
- 子进程终止时;
- 子进程接收到SIGSTOP信号停止时;
- 子进程处于停止态,收到SIGCONT唤醒时。
3.6 信号阻塞
信号阻塞是一个开关动作,指的是阻止信号被处理,而不是阻止信号的产生
信号状态:
- 信号递达:信号的实际处理过程(或略、执行默认动作、捕捉)
- 信号未决:从产生到递达之间的状态
信号集:
sigset set;自定义信号集
int sigemptyset(sigset_t *set);//将set集合置空
int sigaddset(sigset_t *set,int signo);//将signo信号加入到set集合
int sigdelset(sigset_t *set,int signo);//从set集合中移除signo信号
int sigfillset(sigset_t *set); //将所有信号加入set集
int sigismember(const sigset_t *set, int signum); //测试参数signum 代表的信号是否已加入至信号集里
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);//设定对信号集里的信号处理方式
how:
- SIG_BLOCK : 附加set到阻塞表,原来的保存在到oldset
- SIG_UNBLOCK:从阻塞表中删除set中的信号,原来的保存到oldset
- SIG_SETMASK:清空阻塞表并设置为set,原来的保存到oldset
4. 共享内存(内存映射)
优点:实现了用户空间和内核空间的高效交互方式
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
-
- start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。
-
- length:映射区的长度。
-
- prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算(“|”)合理地组合在一起。 PROT_EXEC //页内容可以被执行;PROT_READ //页内容可以被读取;PROT_WRITE //页可以被写入PROT_NONE //页不可访问
-
- flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体。 MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件;MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联,(血缘关系直接的通信)。
-
- fd:有效的文件描述词。匿名映射填-1
-
- offset:被映射对象内容的起点。文件偏移量
成功执行时,mmap()返回被映射区的指针,munmap()返回0。
失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。
注:
-
当MAP_SHARED ,映射区的权限《=文件打开的权限;MAP_PRIVATE 只需要对文件有读权限即可,不会写入磁盘,且不能进行进程直接的通信
-
映射文件大小不能为0,否则报总线错误,
-
文件偏移量必须为0或者4K的倍数,不然报非法参数错误
-
创建映射区的时候,隐含了一次对映射文件的 读取,将文件内容读取到映射区
-
映射区的释放与文件关闭无关,只要映射成功,文件可以立即关闭
-
映射大小可以大于文件大小,但只能访问文件的page的内存地址,否则报总线错误(申请实际最大空间为(文件大小/4K+1)*4K)
-
注意检查返回值
5. 消息队列
是SystemV IPC对象中的一种
消息队列ID来唯一标识
就是一种消息的列表,可以在消息队列中添加、读取消息
可以按照类型来发送、接收消息
发送端:
-
申请key
-
打开/创建消息队列 msgget
-
向消息队列发送消息 msgsend
接收端:
-
打开/创建消息队列 msgget
-
从队列中接收消息 msgrcv
-
控制消息队列 msgctl
申请key
key_t ftok(const char *pathname, int proj_id);
proj_id :取值是1到255
打开/创建消息队列
int msgget(key_t key, int msgflg);
msgflg:IPC_CREAT 创建; IPC_EXCL 检查是否存在; mode 0666
向消息队列发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数msgflg: 是否阻塞 0 阻塞 ;IPC_NOWAIT 不阻塞(队列满时)
数据的缓冲区必须要定义为如下结构体:消息长度不包括首类型long
struct msgbuf {
long mtype; /*数据类型必须大于0, must be > 0 */
char mtext[1]; /*数据,该数组的长度任意定义*/};
从队列中接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
msgtyp = 0:接收任意类型的数据 ;>0:接收指定类型的数据 ; <0:接收小于等于绝对值类型的数据
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
cmd 控制命令 IPC_RMID -》删除
shell命令:ipcs