重点1:进程间的通信方式

答:包含有六种、管道、消息队列、共享内存、信号量、信号、socket。

方式1:管道

管道可以分为匿名管道有名管道。在管道中传输数据是==单向==的。

  • 匿名管道(PiPe):只能用于有血缘关系的进程之间。例如,采用fork函数创建的父子进程。

案例说明:例如在shell中使用|代表左边的输出作为|右边的进程的输入。

1
ps auxf | grep mysql

在代码中创建的方法:

1
int pipe(int fd[2]);// fd[0]代表读 fd[1]代表写

匿名管道作为特殊的文件,只存在于内存,不存在文件系统中。

img

两个描述符都存在于同一个进程中,因此使用fork创建子进程时,创建的子进程会复制父进程的文件描述符。这样就使得两个进程各自有两个fd[0]和fd[1]了。

img
  • 有名管道(FIFO):可以用于不存在血缘关系的进程之间。

创建有名管道的方法:mkfifo,例如

1
mkfifo myPipe

代码中实现的函数:

1
int mkfifo(const char* pathname,mode_t mode)

==注意:==

管道其实本质是内核中的一个缓冲区,因此数据本质是通过内核在管道中流动。

管道的缺点:管道之间的通信效率较低,不适合在进程之间频繁地交换数据

==生命周期:随进程的创建而建立,随进程的结束而销毁。==

方式2:消息队列

管道的通信方式缺点在于通信效率较低,不适合在进程之间频繁地交换数据。==消息队列==的通信模式在于,A进程要给B进程发送消息,A进程把数据放在对应的消息队列,B进程需要的时候去消息队列读取数据就可以了。同理B进程要给A进程发送消息也是一样的。

==消息队列是保存在内核中的消息链表。==在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的数据块,不像管道无格式的字节流数据块

生命周期:==随内核==,如果没有释放消息队列或没有关闭操作系统,消息队列会一直存在。

优点:

  • 相比于管道通信方式,进程之间可以频繁的交换数据。

缺点:

  • 通信不及时。因为消息体是存储在对应的消息队列,从而有一定的延迟。

  • 附件的大小也有限制。不适合较大数据的传输,例如消息的最大长度(MSGMAX)和队列的最大长度(MAXMNB);

  • 消息队列通信过程中,存在用户态和内核态之间的数据拷贝开销。

方式3:共享内存

实现思想:现代操作系统的内存管理,采用的是虚拟内存方式,每个内存都有独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存。==而共享内存的机制,就是将虚拟的地址空间映射到相同的物理内存中。==实现机制如下所示:

img

优点:

  • 避免了消息队列中存在的内核态和用户态的拷贝开销问题

缺点:

  • 进程之间对于同一块物理内存操作时,存在的同步和异步问题

方式4:信号量

解决问题:解决多进程竞争共享资源,导致的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。信号量就实现了这一保护机制。信号量本质是一个整形的计数器,==主要用于实现进程间的互斥和同步==,而不是用于缓存进程之间通信的数据。

实现方法:信号量表示资源的数量,控制信号量的方式有两种原子操作:

  • 一个是P操作,该操作会将信号量减1,相减完如果信号量<0,**表示==**资源已经被占用**==,进程需要==**阻塞等待**==。相减后如果==**信号量>=0==,则表明还有资源可用,进程可以正常执行
  • 一个是V操作,该操作会把信号量加1,相加后如果信号量<=0**,则表明当前**有阻塞的进程**,于是会将该进程唤醒运行;相加后如果**信号量>0,则表明当前没有阻塞的进程

案例1:实现两个进程==互斥==

将信号量设置为1,进程1先进行P操作访问共享内存,此时信号量为0表示资源可用,假设此时进程2进行P操作减1,为-1,处于阻塞状态,直到进程1访问完毕执行V操作,使得进程2才可以访问共享内存。

1
2
3
4
5
6
7
8
9
10
11
s=1; // 初始信号量为0
process1{
P(s);
working...
V(s);
}
process2{
P(s);
working...
V(s);
}

案例2:实现两个进程==同步==

1
2
3
4
5
6
7
8
9
10
11
s=0; // 初始信号量为0
process1{

working...
V(s);
}
process2{

P(s);
working...
}

方式5:信号(==唯一的异步通信机制==)

以上介绍的是常规状态下的工作模式。对于异常情况下的工作模式,就需要用信号的方式来通知进程。Linux中支持的信号有如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

注:查看所有信号的方式:kill -l

产生信号的方式:

  • 硬件来源:ctrl+c产生SIGINT信号,表示==终止==该进程;ctrl+z产生SIGTSTP信号,表示停止该进程,但还未结束。
  • 软件来源:kill -SINGNAL PID

处理信号的方式:

信号是进程间通信机制中==唯一的异步通信机制==,因为可以在任何时候发送信号给某一进程,一旦有信号产生,用户处理信号的方式有以下几种:

1、执行默认操作。Linux中针对每个信号都规定了默认的操作,例如,SIGTERM信号,表示对进程终止的意思。

2、捕捉信号。为信号定义一个信号处理函数。信号发生时,执行默认的信号处理函数。

3、忽略信号。不希望处理某些信号时,可以忽略该信号,不做任何处理

方式6:Socket

解决问题: ** 跨网络与不同主机之间的进程之间通信,就需要socket通信了。(socket也可以用于同一主机**之间的不同进程之间通信)

使用方式:创建socket的系统调用:int socket(int domain, int type, int protocal);

  • domain:用于指示协议族,AF_INET用于IPV4,AF_INET6用于IPV6,AF_LOCAL/AF_UNIX用于本机
  • type:用于指示通信特性,例如SOCK_STREAM表示字节流,对应于TCP;SOCK_DGRAM表示数据报,对应于UDP,SOCK_RAW表示的是原始套接字;
  • protocal:原本用于指示通信协议,但基本废弃,写为0即可。

类型:根据创建的socket类型不同,通信的方式也不同:

  • 实现**TCP字节流**通信:socket类型是AF_INETSOCK_STREAM;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 在服务器段需要
    socket
    bind
    listen
    accept
    read

    // 客户端
    socket
    bind
    connect
    write // 当客户端完成数据传输使会调用close,此时服务端read时会读到EOF,待处理完数据之后会调用close表示连接关闭。
  • 实现**UDP数据包**通信:socket类型是AF_INETSOCK_DGRAM;

    建立UDP通信过程不需要三次握手,也就不需要listenconnect,但是UDP交互过程需要IP和端口号,因此需要bind。对于UDP无需维护连接,因此没有发送方和接收方,只需要一个socket多台机器之间就可以相互通信。每次通信的时候使用sendtorecvfrom都需要传入目标主机的IP地址和端口。

  • 实现本地进程间通信:【本地字节流】AF_LOCALSOCK_STREAM;【本地数据报】AF_LOCALSOCK_DGRAM。另外AF_UNIXAF_LOCAL是等价的,所以AF_UNIX也属于本地socket

    和TCP、UDP最大的区别在于本地字节流socket和本地数据报socketbind的时候不要绑定IP地址和端口,==而是绑定一个本地文件。==