由于进程间空间独立,无法直接通信,所以需要IPC机制实现通信

        同一主机进程间通信

                1,古老的通信方式:无名管道;有名管道;信号

                2,IPC对象通信system v:共享内存(效率最高);消息队列;信号量集(信号灯)

        不同主机之间的通信:socket通信;网络通信

        通信类型

                单工:数据传输是完全单向的,通信双方严格分工,一方固定为发送端(只能发不能收),另一方固定为接收端(只能收不能发),传输方向不可改变,例如广播

                半双工:数据传输允许双向进行,但同一时刻只能单向传输 ,发送和接收功能不能同时开展,需交替进行,例如对讲机    

                全双工:数据传输可同时双向进行,发送端和接收端能独立收发数据,无需等待切换例如打电话

古老方式

管道

        无名管道:只能用于同一主机具有亲缘关系的进程通信(例如父子进程之间)

        有名管道:可以用于同一主机任意进程间通信

无名管道

        本质:内核空间中的一段缓冲区,遵循先进先出特性

        默认大小:64K

        特性:

                1,写堵塞:读端和写端都存在时,向管道中写数据时,当管道满时,发生写阻塞

                2,读阻塞:读端和写端都存在时,向管道中读数据时,若管道为空,发生读阻塞

                3,读返回0:当写端关闭,从管道中读数据,若有数据则读到数据,若没有数据则read返回0,不再阻塞

                4,管道破裂:读端关闭,向管道中写入数据。这是一种异常,操作系统会强制让进程结束

        操作流程:

                1)创建无名管道:pipe()

                2)写管道:write()

                3)读管道:read()

                4)关闭管道:close()

        不必刻意释放,因为属于内核区域,关闭会自动释放

pipe

        参数:

                pipefd[0]为读端描述符

                pipefd[1]为写端描述符

                二者可以作为write()和read()的实参

        返回值:成功则0失败-1

        管道文件应该在fork()之前创建。读写端不能交换

memset()

        功能:将内存区域填充成指定数据

        参数:

                s:要填充的空间首地址

                c:要填充的字符

                n:要填充的字节数

        返回值:成功则为s的首地址,失败则为NULL

        输出4个A,需要注意的是,第三个参数不是sizeof(buff)是因为puts会一直读到\0,如果buff[4]也为A的话,puts会继续往后读,造成数组越界访问

        一般情况下,不会让一个进程既可写又可读,保证管道的方向性,如果要两个进程互相通信,需要构建两个管道

有名管道

        本质:内核空间的一段缓冲区,这段缓冲区和一个管道文件相关联

        操作流程:

                1,创建管道文件:mkififo()

                2,打开管道文件:open()

                3,读或写管道文件:write()

                4,关闭管道文件:close()

                5,删除管道文件:remove()

mkfifo()

        功能:创建管道文件

        参数:管道文件的名称;管道文件的读写执行权限(注意,是读写执行权限,并非打开方式)

        返回值:成功为0失败-1

        open会有阻塞的效果,当以只读的方式打开时,会阻塞直到有进程以写的方式打开,对于只写方式同理

        该例子只能“运行”一次,因为再次运行写端时候会发生如下情况

        这是由于有名管道已经创建,而在写端的代码中再次创建同名管道,属于正常但是错误现象,可以将创建管道的代码单独拿出来运行

有名管道实现全双工
进程

        两端的代码几乎一模一样,无非是所用的管道不同,采用的是父子进程,父进程为读端,子进程为写端

        运行时,在左侧下线时候打出了cd,这串字符串会被存到缓存区,待到程序运行结束时再提出来,这就是为什么最后执行了cd命令,而且自己下线时不代表对面下线,所以此时可以接收对方的信息

        值得说明一下的是wait和waitpid

        先说wait,放在父进程末尾,是在父进程所对应的写端结束时,该父进程也不必作为读端,直接用来阻塞回收子进程即可。

        再说waitpid,如果waitpid放在while之上,那么只会尝试回收这一次,如果此时子进程未结束,父进程就继续往下执行,此时如果子进程结束,那么父进程则不会进行回收。所以waitpid放在循环内,目的是为了随时可以回收自己的子进程,且为非阻塞回收

        如果没有wait只有waitpid,那么写端退出时,所对应的读端父进程则无法回收其子进程。如果没有waitpid只有wait,那么写端子进程退出时,其父进程尚未退出,即未运行到wait,则会导致该写端子进程成为孤儿进程

        总的来说,wait是为了能在父进程被动结束时还留有回收子进程的能力,waitpid是为了子进程主动结束时得以被回收

线程

信号

        实现进程间通知机制,实现进程间的异步通信(接收方不知道什么时候发送方会发送数据),是一种软中断

分类

        系统支持的信号:

                1,kill -l

                        2)SIGINT(crtl + c)让一个进程被打断

                        3)SIGQUIT(ctrl + \)让一个进程结束

                        9)SIGKILL(管理员信号):强制让一个进程结束

                       11)SIGSEGV:让一个进程结束(段错误)

                       13)SIGIPIPE:让一个进程结束(管道破裂)

                       14)SIGALRM:让一个进程结束(定时时间到达)

                       17)SIGCHLD:子进程结束时发送给父进程

                       18)SIGCONT:让停止态的进程继续运行

                       19)SIGSTOP(管理员信号):让运行态的进程进入停止态(暂停)强制停止

                       20)SIGTSTP(ctrl + z):让后台进程进入暂停态,时来自终端的停止信号

                       >34) 用户自定义

                2,信号处理流程

                        在接收到信号时,会优先执行完信号所需处理的事件,再接着执行本来执行的事件

                3,信号处理方式

                        1)缺省:按照默认处理方式

                        2)忽略:不处理

                        3)捕获:以自定义方式处理

                        值得注意的是,管理员信号无法被忽略和捕获,即9和19只能按照默认方式处理

signal

        功能:设置信号的处理方式(注册一个信号)

        参数:

                signum:要处理的信号的编号

                handle:

                        SIG_IGN:以忽略的方式处理该信号,即不处理

                        SIG_DFL:以缺省的方式处理,即系统默认方式

                        函数的地址:以捕获方式处理,即自定义,此函数需要一个整型的形参,而signum注册的信号编号会作为实参上传

        返回值:失败则为NULL;自定义方式时,返回函数的地址

        注意:

                1,若信号不被注册,则按默认方式处理

                2,信号只需注册一次

                3,每次信号的到来,都会触发一次信号处理函数

                4,信号尽可能早注册

        如图,signal按照handler的方式运行,所以此时SIGINT信号并不能打断该进程,即按照系统默认的方式运行

发送信号

        1,kill命令

        2,kill()

                功能:给指定的进程发送一个信号

                参数:接收信号的进程的pid;信号的编号

                返回值:成功则0失败-1

        3,子进程结束时,会发送SIGCHLD(17)信号给父进程

              子进程空间异步回收:通过子进程发送的SIGCGLD信号实现

        4,raise():给自己所在的进程发送信号

        5,alarm()

                功能:设置一个闹钟,闹钟时间到达时,向进程发送一个SIGALRM(14)的信号

                参数:秒数

                返回值:成功则为上次设定剩余的时间,失败则为0

               注:如果定了两个,则第一个会失去效果

                上面的是没有alarm(5)的

        6,pause():让一个进程进入到睡眠状态

                注:pause可以被一个可以被捕获的信号唤醒

IPC

        可以通过ipcs -a查看内核终端IPC对象,-s为删除信号量集,-m+id为删除共享内存

共享内存

        使用内核空间中的内存区域共享数据,采用内存映射技术,减少的数据的反复拷贝,提高了通信效率,本质是通过MMU(内存管理单元)的管理将不同的内存空间映射到同一物理内存区域中,最大为32M

        操作流程:

                1)创建一个IPC key:key_t ftok(const char *pathname, int proj_id)

                2)创建共享内存:shmget

                3)建立共享内存段和用户空间的内存映射:shmat

                4)向共享内存写入数据:通过用户空间首地址

                5)解除映射关系:shmdt

                6)删除共享内存:shmctl

ftok

        功能:创建一个IPC key

        参数:

                pathname:路径,随便写,只需在两个进程之间创建key时传递的路径相同

                proj_id:工程ID,与其他key不冲突,一般来说会用特殊的字符表示,例如'!'

        返回值:成功则为IPC key,失败则为-1

        注意:两个进程在创建KEY时,必须使用相同的参数

shmget

        功能:创建一个共享内存

        参数:

                key:IPC key

                size:共享内存大小,会被扩展成PAGE_SIZE(4096byte)的整数倍

                shmflg:IPC_CREAT|0664(创建并给予权限)

        大小不超过1G,因为内核大小只有1G,而且一般情况下共享内存最大500M

        返回值:成功则为共享内存的ID,失败则为-1

shmat

        功能:建立共享内存映射

        参数:

                shmid:共享内存的id

                shmaddr:映射的用户空间首地址,如果是NULL则是让操作系统分配

                shmflg:

                        SHM_RDONLY:只读

                        !SHM_RDONLY:可读可写

        返回值:成功则为映射的用户空间首地址,失败则为(void *)-1

shmdt

        功能:解除内存映射关系

        参数:要解除的用户空间首地址

        返回值:成功为0失败-1

shmctl

        功能:操作共享内存

        参数:

                shmid:要操作的共享内存id

                cmd:要执行的操作指令

                        IPC_RMID:删除操作

                buf:设置的参数(删除的时候不需要)

                返回值:成功为0失败-1

共享内存实现单工

        其中那个“第一服务器”只是个名字,在本目录下必须有这么个文件才可以,不然程序会报错

信号量集

        实现进线程间同步

消息队列

        和管道类似,有同步的效果,相对于管道而已增加了数据的等级,可以优先处理优先级较高的数据

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐