嵌入式笔记:进程间的通信

为什么需要进程间通信(IPC)?

1、数据传输

    一个进程需要将它的数据发送给另一个进程。(进程要合作)

2、资源共享

    多个进程之间共享同样的资源。(要协作)

3、通知事件

    一个进程需要向另外一个或一组进程发送消息,通知它们发生了某种事件(进程同步)

4、进程控制

Linux使用的进程间通信方式包括:

1、管道(pipe)和有名管道(FIFO)

2、信号(signal)

3、消息队列

4、共享内存

5、信号量

6、套接字(socket)

一、管道通信

    管道是单向的、先进先出,它把一个进程的输出和另一个进程的输入连接在一起。写进程在管道的尾部写入数据,读进程在管道的头部读出数据。

     管道分为两种:无名管道和有名管道,前者是用于父子进程之间通信,后者可以用于任意两个进程之间通信。

     无名管道由pipe()函数创建

     int pipe(int filedis[2]);

     当一个管道建立时,它会创建两个文件描述符:filedis[0]用于读管道(管道头部),filedis[1]用于写管道(管道尾部)

   

     管道关闭

     int close(int fd);

     管道读写

     管道用于不同进程间的通信。通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。

      注意:必须在系统调用fork()前调用pipe(),否则子进程将不会继承文件描述符。如果pipe()在fork()之后调用会创建两个管道。父子进程要互相通信必须使用同一个管道。

    创建管道之后会创建两个文件描述符filedis[0]、filedis[1],然后就可以用read、write系统调用读写管道。   

    例如:

    read(filedis[0], buf_r, sizeof(buf_r);    //读管道

    write(filedis[1], "hello world", 12);     //写管道

    步骤:首先创建管道,然后利用fork创建子进程(子进程会继承父进程创建的管道的文件描述符,此时两个进程都使用同一个管道),利用文件操作的读(read)写(write)函数对管道进行读写即可。

命名管道(FIFO)

命名管道和无名管道的不同点在于无名管道只能父子进程之间通信,但是命名管道是可以在同一个操作系统之间任意进程通信。

命名管道的创建(实际上命名管道就是一个文件)

int mkfifo(const char *pathname, mode_t mode)

*pathname: FIFO文件名

*mo de: 属性

创建了一个FIFO(命名管道),相当于creat创建了一个文件就可以用一般的文件访问的函数(open、write、read、close等)使用FIFO

当打开FIFO时,非阻塞标志(O_NONBLOCK)

1、没有使用O_NONBLOCK:访问要求无法满足时,进程将阻塞。如试图读取空的FIFO,将导致进程阻塞。

2、使用了O_NONBLOCK:访问要求无法满足时不阻塞,立刻出错返回,errno是ENXIO

一般步骤:

先定一个文件“/tmp/myfifo"

先在一个进程中用mkfifo创建一个命名管道(FIFO)

再用open打开FIFO

再用write、read系统调用读写这个FIFO

在第二个进程中也打开文件“/tmp/myfifo"

然后在调用write、read系统调用去读写这个FIFO,当读完这个管道之后,里面的数据就被取走了管道就为空了。

像一个管子一样,往里面放东西,然后取走之后就没了。

二、信号通信

如:进程用kill函数将信号发送给另外一个进程,用户也可用kill命令将信号发送给其他进程

信号类型

常见的信号类型

信号的处理方式 

1、忽略此信号

有两种信号是不能忽略的:一种是SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是用来终止和停止进程的

2、执行用户希望的动作

通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理

3、执行系统的默认操作

对大多数信号的系统默认动作是终止该进程

信号的放函数有:kill和raise

区别:kill既可以向自身进程发送信号,也可以向其他进程发送新型号。

            raise函数是向进程自身发送信号

           

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
int raise(int sig);

pause函数使调用进程挂起直至捕捉到一个信号(然后去信号处理函数中处理)

#include <unistd.h>

int pause(void)

只有执行了一个信号处理函数后,挂起才结束。

三、共享内存

 共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法。

共享内存实现分为两个步骤:

一、创建共享内存,使用shmget函数。

二、映射共享内存,将这段共享内存映射到具体的进程空间去,使用shmat函数 

/*正确函数*/
void *shmat(int shmid, const void *shmaddr, int shmflg);

 int shmdt(const void *shmaddr);


 

 

四、消息队列

信号能够传送信息量有限,管道只能传送无格式的字节流。

消息队列就是一个消息链表。可以把消息看作一个记录,具有特定的格式。进程可以向消息队列中按照一定的规则添加新消息(进队列)。另外一些进程可以从消息队列中读走消息(出队列)。

消息队列键值(每个消息队列都有一个唯一的键值),要想获得一个消息队列的描述字,必须提供该消息队列的键值。

  

 

 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                      int msgflg);

 

 

 

 

五、信号量

信号量与其他进程间通信方式不大相同,主要用途是保护临界资源。进程可以根据它判断是否能够访问某些共享资源。除了用于访问控制外,还可以用于进程同步。

临界资源:在多任务环境中,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。例如:打印机

信号量是实质就是一个整数(初始值为1表示临界资源现在可用)

给临界资源添加一个信号量,假设有两个进程A和B都要用这个临界资源,那么在访问这个临界资源之前,要先获取这个临界资源的信号量。检测信号量的值是否大于0,如果是大于0的就将信号量减一,然后就访问临界资源。

假设现在A进程要访问临界资源S,那么A进程先获取临界资源S的信号量M并判断M的值是否大于0,如果大于0(临界资源可访问)那么就将信号量M减一,然后就访问这个临界资源。此时,进程B也想访问这个临界资源S,那么进程B也要先获取临界资源S的信号量M,此时M为0(表示临界资源不可访问),那么B进程就阻塞(直至A进程释放临界资源S(信号量M加一)才唤醒B进程),

信号量分类:二值信号量(只能取0或1)、任意时刻只允许一个进程访问临界资源

                        计数信号量(任意非负值)、如果信号量大于1,可以允许多个进程访问临界资源

 

 

 

猜你喜欢

转载自blog.csdn.net/wllen_/article/details/80379491