2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结

随心而作 2019-12-01

目录

一、Linux进程间通信(一)——管道

1.管道概述

管道是Linux中进程间通信的一种方式。这里所说的管道主要指无名管道,它具有以下特点:

  • 它只能用于父子进程等具有亲缘关系的进程之间的通信
  • 它是一个半双工的通信模式,具有固定的读端和写端。双方通信时,需要建立起两个管道。
  • 管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read()和write()等函数。
  • 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

2.管道系统调用

(1)创建管道

  • man -k pipe|grep create

    2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结

  • man 2 pipe

    2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结

  • int pipe(int pipefd[2]);

    2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结
    当一个管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,而fd[1]固定用于写管道,这样就构成了一个半双工的通道。

(2)创建子进程

通过fork()函数创建一子进程,该子进程会继承父进程所创建的管道。

(3)关闭相应描述符

  • 为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。例如在下图中将父进程的写端fd[1]和子进程的读端fd[0]关闭。此时,父子进程之间就建立起了一条子进程写入父进程读取的通道。

    2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结

  • 注意:
    当一个管道共享多对文件描述符时,若将其中一对读写文件描述符都删除,则该管道就失效。

3.标准流管道

(1)popen()函数简介

标准流管道将一系列的创建过程合并到一个函数popen()中完成。它所完成的工作有以下几步:

  • 创建一个管道
  • fork()一个子进程
  • 在父子进程中关闭不需要的文件描述符
  • 执行exec函数族调用
  • 执行函数中所指定的命令

(2)popen()函数详解

  • 使用man 3 popen查看函数的详细使用方法如下图:

    2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结

  • command:指向的是一个以null 结束符结束的字符串,这个字符串包含一个shell命令,由shell来执行;
  • type:“r”:文件指针连接到command 的标准输出,即该命令的结果产生输出
    “w”:文件指针连接到command 的标准输入,即该命令的结果产生输入
  • 关闭用popen()创建的流管道必须使用函数pclose()来关闭该管道流

4.管道的局限性

  • 只支持单向数据流
  • 只能用于具有亲缘关系的进程之间
  • 没有名字,不方便操作
  • 管道的缓冲区是有限的

二、Linux进程间通信(二)——有名管道

1.有名管道概述

有名管道(named pipe或FIFO)克服了无名管道只能用于具有亲缘关系的进程之间、没有名字的局限性。

  • FIFO提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。因此,通过FIFO不相关的进程也能交换数据。
  • FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
  • 不支持诸如lseek()等文件定位操作。

2.有名管道的相关操作

  • 创建有名管道

    • man -k pipe | grep named
      2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结
    • man 3 mkfifo
    • int mkfifo(char *pathname,mode_t mode);
      2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结
      2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结
    • 创建成功后,使用open()、read()和write()函数进行相关操作

三、Linux进程间通信(三)——信号量

1.信号量概述

信号量是用来解决进程之间的同步与互斥问题的一种进程之间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(PV操作)。其中信号量对应于某一种资源,取一个非负的整型值。信号量值指的是当前可用的该资源的数量,若它等于0 则意味着目前没有可用的资源。

PV原子操作的具体定义如下:

  • P 操作:如果有可用的资源(信号量值>0),则占用一个资源(信号量值-1,进入临界区代码);如果没有可用的资源(信号量值等于0),则被阻塞,直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程);
  • V 操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程。如果没有进程等待它,则释放一个资源(信号量值+1)。

2.信号量的使用步骤

(1)创建信号量

  • man –k semaphore
  • man 2 semget
  • int semget(key_t key,int nsem,int semflg);

    2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结

参数详解

  • key:信号量的键值,多个进程可以通过它访问同一个信号量,其中有个特殊值IPC_PRIVATE。它用于创建当前进程的私有信号量。
  • nsems:需要创建的信号量数目,通常取值为1。
  • semflg:同open()函数的权限位,也可以用八进制表示法,其中使用IPC_CREAT标志创建新的信号量,即使该信号量已经存在(具有同一个键值的信号量已在系统中存在),也不会出错。如果同时使用IPC_EXCL 标志可以创建一个新的唯一的信号量,此时如果该信号量已经存在,该函数会返回出错。

(2)初始化信号量

使用semctl()函数的SETVAL操作

2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结

(3)进行信号量的PV 操作

调用semop()函数

2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结

(4)删除不再使用的信号量
使用semclt()函数的IPC_RMID 操作

四、教材实例分析

  • 源代码:
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<stdlib.h>
#include<unistd.h>
#define DELAY_TIME 3

int main()
{
    pid_t result;
    int sem_id;
    sem_id=semget(ftok(".",'a'),1,0666|IPC_CREAT);
    init_sem(sem_id,0);
    result=fork();
    if(result==-1)
    {
        printf("Error\n");
    }
    else if(result==0)
    {
        printf("Child process will wait for some second...\n");
        sleep(DELAY_TIME);
        printf("The returned value is %d in the child process(PID=%d)\n",result,getpid());
    //  sem_v(sem_id);
    }
    else{
    //  sem_p(sem_id);
        printf("The returned value is %d in the father process(PID=%d)\n",result,getpid());
    //  sem_v(sem_id);
    //  del_sem(sem_id);
    }
    exit(0);
}
union semum
{
    int val;
    struct sem_id_ds *buf;
    unsigned short *array;
};
int init_sem(int sem_id,int init_value)
{
    union semum sem_union;
    sem_union.val=init_value;
    if(semctl(sem_id,0,SETVAL,sem_union)==-1)
    {
        perror("Initialize semaphore");
        return -1;
    }return 0;
}
int del_sem(int sem_id)
{
    union semum sem_union;
    if(semctl(sem_id,0,IPC_RMID,sem_union)==-1)
    {
        perror("Delete semaphore");
        return -1;
    }
    return 0;
}
int sem_p(int sem_id)
{
    struct sembuf sem_b;
    sem_b.sem_num=0;
    sem_b.sem_op=-1;
    sem_b.sem_flg=SEM_UNDO;
    if(semop(sem_id,&sem_b,1)==-1)
    {
        perror("P operation\n");
        return -1;
    }return 0;
}

int sem_v(int sem_id)
{
    struct sembuf sem_b;
    sem_b.sem_num=0;
    sem_b.sem_op=1;
    sem_b.sem_flg=SEM_UNDO;
    if(semop(sem_id,&sem_b,1)==-1)
    {
        perror("P operation\n");
        return -1;
    }return 0;
}
  • 运行结果如下图:

    2019-2020-1 20175313 《信息安全系统设计基础》第十周学习总结

  • 分析:没有使用信号量时,进程是并发的,谁先执行由系统本身决定,而加上信号量之后,由于信号量的初始值为0,所以必须等到值为1时,才能执行sem_p,因此需要先让子进程执行sem_v,因此先输出子进程,在输出父进程。

五、参考资料

  • Linux进程间通信(一)——管道
  • Linux进程间通信(二)——有名管道
  • Linux进程间通信(三)——信号量

相关推荐