zoukankan      html  css  js  c++  java
  • 实验八 进程间通信

    实验八 进程间通信

    项目内容
    这个作业属于哪个课程 Linux系统与应用
    这个作业的要求在哪里 作业要求
    学号-姓名 17041526-钟大胜
    作业学习目标 1.了解进程间通信的常用方式; 2掌握管道、消息队列、信号量、共享内存实现进程间通信的方法。

    1. 举例说明使用匿名管道进行进程通信。

    匿名管道:
    当进程使用 pipe 函数,就可以打开位于内核中的这个特殊“文件”。同时 pipe 函数会返回两个描述 符,一个用于读,一个用于写。

    如果你使用 fstat 函数来测试该描述符,可以发现此文件类型为 FIFO 。而无名管道的无名,指的就是这个虚幻的“文件”,它没有名字。
        man 2 pipe

    pipe 函数打开的文件描述符是通过参数(数组)传递出来的,而返回值表示打开成功(0)或失败 (-1)。
    它的参数是一个大小为 2 的数组。此数组的第 0 个元素用来接收以读的方式打开的描述符,而第 1 个元素用来接收以写的方式打开的描述符。
    也就是说, pipefd[0] 是用于读的,而 pipefd[1] 是用于写的。
    打开了文件描述符后,就可以使用 read(pipefd[0]) 和 write(pipefd[1]) 来读写数据了。

    注意事项:这两个分别用于读写的描述符必须同时打开才行,否则会出问题。

    如果关闭读 ( close(pipefd[0]) ) 端保留写端,继续向写端 ( pipefd[1] ) 端写数据( write 函数)的进程会收到 SIGPIPE 信号。
    如果关闭写 ( close(pipefd[1]) ) 端保留读端,继续向读端 ( pipefd[0] ) 端读数据( read 函数),read 函数会返回 0.

    例题:父进程 fork 出一个子进程,通过无名管道向子进程发送字符,子进程收到数据后将字符串中的小写字符转换成大写并输出。

    2. 举例说明使用 mkfifo 命令创建命名管道以及简单演示管道如何工作。

     命名管道

    1.通过命令 mkfifo 创建管道

        man mkfifo

    2.通过函数 mkfifo(3) 创建管道

        man 3 mkfifo

    FIFO文件的特性
    a) 查看文件属性
    当使用 mkfifo 创建 hello 文件后,查看文件信息如下:

     

    某些版本的系统在 hello 文件后面还会跟着个 | 符号,像这样 hello|
    b) 使用 cat 命令打印 hello 文件内容

    可以看到cat已经被堵塞了。

    开启另一个终端,执行

    然后你会看到被阻塞的 cat 又继续执行完毕,在屏幕打印 “hello world” 。
    如果你反过来执行上面两个命令,会发现先执行的那个总是被阻塞

    c) fifo 文件特性
    根据前面两个实验,可以总结:
    (1)文件属性前面标注的文件类型是 p ,代表管道
    (2)文件大小是 0
    (3)fifo 文件需要有读写两端,否则在打开 fifo 文件时会阻塞
    如果在 open 的时候,使用了非阻塞方式,肯定是不会阻塞的。
    特别地,如果以非阻塞写的方式 open ,同时没有进程为该文件以读的方式打开,会导致 open 返回错误(-1),同时 errno 设置成ENXIO.

    3. 编写两个程序使用第2题中创建的管道进行通信。

     例题:编写两个程序,分别是发送端 pipe_send 和接收端面 pipe_recv 。程序 pipe_send 从标准 输入接收字符,并发送到程序 pipe_recv ,同时 pipe_recv 将接收到的字符打印到屏幕

    分别开启两个终端,分别运行pipe_send和pipe_recv:

    现在两个终端都处于阻塞状态,在运行pipe_send的终端输入数据,然后就可以在运行的pipe_recv的终端看到相应的输出:

    使用组合按键结束上述进程。

    4. 编写两个程序分别通过指定的键值创建 IPC 内核对象,以及获取该指定键值的 IPC 内核对象。

    IPC 内核对象

    每个 IPC 内核对象都是位于内核空间中的一个结构体。具体的对于共享内存、消息队列和信号量,他们在内核空间中都有对应的结构体来描述。
    当你使用 get 后缀创建内核对象时,内核中就会为它开辟一块内存保存它。只要你不显式删除该内核对象,它就永远位于内核空间中,除非你关机重启。

    进程空间的高 1G 空间( 3GB-4GB )是内核空间,该空间中保存了所有的 IPC 内核对象。
    上图给出不同的 IPC 内核对象在内存中的布局(以数组的方式),实际操作系统的实现并不一定是数组,也可能是链表或者其它数据结构等等。
    每个内核对象都有自己的 id 号(数组的索引)。此 id 号可以被用户空间使用。所以只要用户空间知道了内核对象的 id 号,就可以操控内核对象了。
    为了能够得到内核对象的 id 号,用户程序需要提供键值—— key ,它的类型是 key_t ( int 整型)。
    系统调用函数( shmget , msgget 和 semget )根据 key ,就可以查找到你需要的内核 id号。
    在内核创建完成后,就已经有一个唯一的 key 值和它绑定起来了,也就是说 key 和内核对象是一 一对应的关系。(key = 0为特殊的键,它不能用来查找内核对象)

    创建 IPC 内核对象

    man 2 shmget

    man 2 msgget

    man 2 semget

    在创建 IPC 内核对象时,用户程序一定需要提供 key 值才行。
    实际上,创建 IPC 内核对象的函数和获取内核对象 id 的函数是一样的,都是使用 get 后缀函数。
    比如在键值 0x8888 上创建 ipc 内核对象, 并获取其 id ,应该像下面这样:

    // 在 0x8888 这个键上创建内核对象,权限为 0644,如果已经存在就返回错误。
    int id = shmget(0x8888, 4096, IPC_CREAT | IPC_EXCL | 0644);
    int id = msgget(0x8888, IPC_CREAT | IPC_EXCL | 0644); 
    int id = semget(0x8888, 1, IPC_CREAT | IPC_EXCL | 0644); 
    // 第二个参数表示创建几个信号量

    例题:程序 ipccreate 用于在指定的键值上创建 ipc 内核对象。使用格式为 ./ipccreate ,比如./ipccreate 0 0x8888 表示在键值 0x8888 上创建共享内存。

    获取ipc内核对象

    程序 ipcget 用于在指定的键值上获取 ipc 内核对象的 id 号。
    使用格式为 ./ipcget ,比如./ipcget 0 0x8888 表示获取键值 0x8888 上的共享内存 id 号。

    5. 编写一个程序可以用来创建、删除内核对象,也可以挂接、卸载共享内存,还可以打印、设置内核 对象信息。

    前面已经知道如何创建内核对象,接下来分别了解三种内核对象的操作:

    man 2 shmop

    man 2 shmctl

    例题:编写一个程序 shmctl 可以用来创建、删除内核对象,也可以挂接、卸载共享内存,还可以打印、设置内核对象信息。具体使用方法具体见下面的说明:

    ./shmctl -c : 创建内核对象。
    ./shmctl -d : 删除内核对象。
    ./shmctl -v : 显示内核对象信息。
    ./shmctl -s : 设置内核对象(将权限设置为 0600 )。
    ./shmctl -a : 挂接和卸载共享内存(挂接 5 秒后,再执行 shmdt ,然后退出)。

    先在另一个终端执行 ./shmctl -a ,然后(5s内)立即在当前终端执行 ./shmctl -v

    先在另一个终端执行 ./shmctl -a ,运行结束后,然后在当前终端执行 ./shmctl -v

    6. 编写两程序分别用于向消息队列发送数据和接收数据。 msg_send 程序定义了一个结构体 Msg , 消息正文部分是结构体 Person 。该程序向消息队列发送了 10 条消息。

    消息队列

    消息队列本质上是位于内核空间的链表,链表的每个节点都是一条消息。
    每一条消息都有自己的消息类型,消息类型用整数来表示,
    而且必须大于 0.每种类型的消息都被对应的链表所维护,
    下图 展示了内核空间的一个消息队列:

    其中数字 1 表示类型为 1 的消息,数字2、3、4 类似。彩色块表示消息数据,它们被挂在对应类型的链表上。
    值得注意的是,刚刚说过没有消息类型为 0 的消息,
    实际上,消息类型为 0 的链表记录了所有消息加入队列的顺序,其中红色箭头表示消息加入的顺序。

    消息队列相关的函数

    man 2 msgop

    消息数据格式

    无论你是发送还是接收消息,消息的格式都必须按照规范来。简单的说,它一般长成下面这个样子:

    struct Msg{ 
        long type; // 消息类型。这个是必须的,而且值必须 > 0,这个值被系统使用 
        // 消息正文,多少字节随你而定 
        // ... 
    }

    例题:程序 msg_send 和 msg_recv 分别用于向消息队列发送数据和接收数据。 msg_send 程序定义了一个结构体 Msg ,消息正文部分是结构体 Person 。该程序向消息队列发送了 10 条消息。

    程序 msg_send 第一次运行完后,内核中的消息队列大概像下面这样:

    msg_recv 程序接收一个参数,表示接收哪种类型的消息。比如 ./msg_recv 4 表示接收类型为 4 的消息,并打印在屏幕。

    先运行./msg_send,再运行./msg_recv

    接收所有消息:

    接收类型为 4 的消息,这时要重新运行 ./msg_send :

    接收类型小于等于 3 的所有消息,这是不用再运行 ./msg_send :

    还有一个函数来操作消息队列内核对象的

    man 2 msgctl

    7. 编写程序举例说明信号量如何操作。

    信号量

    设置和获取信号量值的函数 semctl :

    man 2 semctl

    请求和释放信号量 semop

    man 2 semop

    struct sembuf { 
        unsigned short sem_num; /* semaphore number */ 
        short sem_op; /* semaphore operation */ 
        short sem_flg; /* operation flags */
    }

    例题:信号量操作 示例

    8. 编写程序使用信号量实现父子进程之间的同步,防止父子进程抢夺 CPU 。 

    例题:使用信号量实现父子进程之间的同步,防止父子进程抢夺 CPU 。

    这里可以看到字符是成对出现的,如果修改程序把64行 sem_p(); 和71行 sem_v();
    注释掉,在编译运行会发现字符可能就不会成对出现了,这里就是用信号量来帮我们实现进程间的同步的

  • 相关阅读:
    Could A New Linux Base For Tablets/Smartphones Succeed In 2017?
    使用libhybris,glibc和bionic共存时的TLS冲突的问题
    6 Open Source Mobile OS Alternatives To Android in 2018
    Using MultiROM
    GPU drivers are written by the GPU IP vendors and they only provide Android drivers
    Jolla Brings Wayland Atop Android GPU Drivers
    How to Use Libhybris and Android GPU Libraries with Mer (Linux) on the Cubieboard
    闲聊Libhybris
    【ARM-Linux开发】wayland和weston的介绍
    Wayland and X.org problem : Why not following the Android Solution ?
  • 原文地址:https://www.cnblogs.com/zds1526/p/12982131.html
Copyright © 2011-2022 走看看