进程间的通信方式。
进程间的通信(IPC
):数据传输、通知事件、资源共享、进程控制。
信号; //Unix
管道; //Unix
有名管道(FIFO); //Unix
消息队列;
共享内存;
信号量;
Socket套接字。
用于有亲缘关系的进程间通信;
一个管道是一个字节流;
通过管道传递的数据是顺序的,从管道中读取出来字节的顺序和被写入管道的顺序是完全一样的(类似一个队列);
管道中数据传递方向是单向的,一端写入一端读取,是半双工的;
管道读数据是一次性的。
终端命令:
ls | wc -l //wc——统计文件数目
ulimit -a //查看管道缓冲大小
函数:
int pipe(int pipefd[2]);//创建一个匿名管道,用来进程间通信。
long fpathconf(int fd, int name);//查看管道缓冲大小
【注】为什么管道用于有亲缘关系的进程间通信(为什么亲缘关系间的进程能通过管道通信)?
因为父进程fork出来一个子进程,会把虚拟地址空间复制一份,父进程中的这个虚拟地址空间中有一个文件描述符表,指向读端和写端,那么子进程复制出来也有一个文件描述符表,指向读端和写端,所以能进行通信。
【注】为什么要一个关闭读端一个要关闭写端?
为了避免父(子)进程写之后父(子)进程又读。
FIFO
有名管道提供了一个路径名与之关联,所以FIFO创建的进程不存在亲缘关系的限制,进程只要能访问该路径,就能通过FIFO相互通信。
终端命令:
mkfifo 名字 //创建FIFO管道
函数:
int mkfifo(const char *pathname, mode_t mode); //创建FIFO管道
管道的读写特点:
读管道:
管道中有数据:
read返回实际读到的字节数
管道中无数据:
写端被全部关闭,read返回0(相当于读到文件的末尾)
写端没有完全关闭,read阻塞等待
写管道:
读端全部被关闭:
进程异常终止(进程收到SIGPIPE信号)
读端没有全部关闭:
管道已满,write阻塞
管道没有满,write将数据写入,并返回实际写入的字节数
(I/O)将磁盘文件数据映射到内存,通过修改内存可修改文件。
内存映射实现的进程通信是非阻塞的。
函数:
//将一个文件或者设备的数据映射到内存中
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
//释放内存映射
int munmap(void *addr, size_t length);
共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。
无需内核介入。
终端命令:
ipcs -a //打印所有ipc信息
ipcs -m //打印共享内存ipc信息
ipcs -q //打印消息队列ipc信息
ipcs -s //打印信号ipc信息
ipcrm -M shmkey //移除shmkey创建的共享内存段
ipcrm -m shmid //移除shmid标识的共享内存段
ipcrm -Q msgkey //移除msgkey创建的消息队列
ipcrm -q msgid //移除msgid标识的消息队列
ipcrm -S semkey //移除semkey创建的信号
ipcrm -s semid //移除semid标识的信号
函数:
//创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。新创建的内存段中的数据都会被初始化为0
int shmget(key_t key, size_t size, int shmflg);
//和当前的进程进行关联
void *shmat(int shmid, const void *shmaddr, int shmflg);
//通信 addr
//释放:
//解除当前进程和共享内存的关联
int shmdt(const void *shmaddr);
//删除共享内存,只调用一次,所有的关联进程都解除了关联才调用
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
操作系统如何知道一块共享内存被多少个进程关联?
答: 共享内存维护了一个结构体struct shmid_ds,
这个结构体中有一个成员 shm_nattch,
shm_nattach 记录了关联的进程个数
信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。
信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
信号的特点:
简单;不能携带大量信息;满足某个特定条件才发送;优先级比较高。
终端命令:
kill -l //查看所有信号
常用的信号:
SIGINT //终止进程——ctrl+C
SIGQUIT //终止进程——ctrl+\
SIGKILL //杀死进程
SIGCSTOP //停止进程
SIGCONT //继续进程
信号的 5 种默认处理动作:
Term 终止进程
Ign 当前进程忽略掉这个信号
Core 终止进程,并生成一个Core文件,用于保存错误信息 //
Stop 暂停当前进程
Cont 继续执行当前被暂停的进程
【注】Core的使用:
ulimit -a
ulimit -c unlimited //更改可使用的资源上限
g++ ./a.out -g
gdb a.out core-file core //查看core文件的错误信息
信号的状态:产生、未决(信号产生了没被处理)、阻塞(阻塞信号被处理,不阻塞信号产生)。
int kill(pid_t pid, int sig);//给任何的进程或者进程组pid, 发送任何的信号 sig
int raise(int sig);//给当前进程发送信号
void abort(void);//发送SIGABRT信号给当前的进程,杀死当前进程
//设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,函数会给当前的进程发送一个信号:SIGALARM,终止当前进程
unsigned int alarm(unsigned int seconds);
alarm(0); //取消定时器
//设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时(每隔几秒钟做一件事)
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
//设置某个信号的捕捉行为
sighandler_t signal(int signum, sighandler_t handler);
//检查或者改变信号的处理。信号捕捉
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
【注】使用SIGCHLD
信号解决僵尸进程的问题。
多个信号可使用一个称之为信号集的数据结构来表示。
int sigemptyset(sigset_t *set);//清空信号集中的数据,将信号集中的所有的标志位置为0
int sigfillset(sigset_t *set);//将信号集中的所有的标志位置为1
int sigaddset(sigset_t *set, int signum);//设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
int sigdelset(sigset_t *set, int signum);//设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
int sigismember(const sigset_t *set, int signum);//判断某个信号是否阻塞
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);//将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
int sigpending(sigset_t *set);//获取内核中的未决信号集