【牛客网C++服务器项目学习】Day6-有名管道、内存映射、信号机制

项目学习地址:【牛客网C++服务器项目学习】

  1. 有名管道:
  • 有名管道特点:

    • 有名管道是FIFO文件,存在于文件系统中,可以通过文件路径名来指出。

    • 管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。

    • 有名管道可以在不具有亲缘关系的进程间进行通信。

  • 相关接口:

    • 在shell中使用mkfifo 命令
      mkfifo filename

    • int mkfifo(const char *pathname, mode_t mode);

      • pathname:即将创建的FIFO文件路径,如果文件存在需要先删除。
      • mode:和open()中的参数相同。
  • 使用方式:

    • 1)使用open函数打开管道文件。如果一个进程以只读(只写)打开,那么这个进程会被阻塞到open,直到另一个进程以只写(只读)或者读写。

    • 2)使用read函数读取内容。read读取普通文件,read不会阻塞。而read读取管道文件,read会阻塞运行,直到管道中有数据或者所有的写端关闭。

    • 3)使用write函数发送内容,使用close函数关闭打开的文件。

  • 有名管道和无名管道的异同点

    • 1、相同点

      • open打开管道文件以后,在内存中开辟了一块空间,管道的内容在内存中存放,有两个指针—-头指针(指向写的位置)和尾指针(指向读的位置)指向它。读写数据都是在给内存的操作,并且都是半双工通讯。
      • 2、区别。有名在任意进程之间使用,无名在父子进程之间使用。
    • 拓展:全双工、半双工、单工通讯的区别:

      • 单工:方向是固定的,只有一个方向可以写,例如广播。

      • 半双工:方向不固定,但在某一刻只能有一个方向进行写,例如对讲机。

      • 全双工:两个方向都可以同时写,例如打电话。

  1. 内存映射函数 mmap()函数
  • 概述
    内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。与此同时,两个进程,都对同一个文件进行内存映射的话,相当于使用共享内存进程IPC通信。
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
  • 功能:将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。

  • 参数:

    • 参数length:代表将文件长度映射到内存。

    • 参数prot:映射区域的保护方式。可以为以下几种方式的组合:
      PROT_EXEC 映射区域可被执行
      PROT_READ 映射区域可被读取
      PROT_WRITE 映射区域可被写入
      PROT_NONE 映射区域不能存取

    • 参数flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
      MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
      MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
      MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
      MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
      MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
      MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。

    • 参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。

    • 参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。

  • 返回值:若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

  • 内存映射的步骤:

    • 用open系统调用打开文件, 并返回描述符fd.

    • 用mmap建立内存映射, 并返回映射首地址指针start.

    • 对映射(文件)进行各种操作, 显示(printf), 修改(sprintf).

    • 用munmap(void *start, size_t lenght)关闭内存映射.

    • 用close系统调用关闭文件fd.

  • UNIX网络编程第二卷进程间通信对mmap函数进行了说明。该函数主要用途有三个:
    1、将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能;
    2、将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
    3、为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。

  1. 信号
  • 信号的概念

信号机制是一种使用信号来进行进程之间传递消息的方法,信号的全称为软中断信号,简称软中断。信号的本质是软件层次上对中断的一种模拟(软中断)。它是一种异步通信的处理机制,事实上,进程并不知道信号何时到来。

在头文件<signal.h>中定义了64种信号,这些信号的名字都以SIG开头,且都被定义为正整数,称为信号编号。可以用“kill -l”查看信号的具体名称。

信号是linux系统为了响应某些状况而产生的事件。进程收到信号后应该采取相应的动作

  • 哪些情况会引发信号

1.键盘事件 ctrl +c ctrl +\

2.非法内存 如果内存管理出错,系统就会发送一个信号进行处理

3.硬件故障 同样的,硬件出现故障系统也会产生一个信号

4.环境切换 比如说从用户态切换到其他态,状态的改变也会发送一个信号,这个信号会告知给系统

怎样查看信号呢?

下面我把最常用到的信号给大家解释一下

(2) SIGINT ctrl +c 终止信号

(3) ctrl +\ 暂停信号,放入后台

(4) 非法指令

(5) abort 进程异常终止

(7) SIGBUS (虚实关系建立) 总线错误(从写的位置到物理内存,操作系统没有将磁盘的开始位置到物理内存之间建立 联系 mmap(把虚拟内存和磁盘文件的关系映射起来,如果磁盘大小大于0,就建立这种关系

(9) SIGKILL kill - 9 pid 杀死进程

(11) SIGSEGV 段错误

(13) 管道破裂

(14)闹钟

(15) 缺省终止某个进程,终止掉

(17)子进程死的时候会给父进程发送这个信号

(19)进程暂停

(23) SIGURG 紧急数据

(29) 异步 IO

进程收到信号的三种处理方式

默认:如果是系统默认的话,那就会终止这个进程

忽略 :信号来了我们不处理,装作没看到 SIGKILL SIGSTOP 不能忽略

捕获并处理 :当信号来了,执行我们自己写的代码(捕获信号这个动作是需要我们完成的) SIGKILL SIGSTOP 不能捕获

发送信号:

kill -信号值 pid

int kill(int pid,int signum)

pid > 0 :发送给pid进程

pid = 0 :调用者所在进程组的任一进程

pid = -1: 有权发送的任何一个进程,除了1

pid < -1 |pid|进程组所有的进程

进程组:进程组中有若干个进程

用管道连接的进程, fork创建的父子进程都属于同一个进程组

给自己发信号

raise( int signum )

kill(getpid() ,signum)

  1. 闹钟程序alarm()

(1)引用头文件:#include <unistd.h>;

(2)函数标准式:unsigned int alarm(unsigned int seconds);

(3)功能与作用:alarm()函数的主要功能是设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒数后发送给目前的进程。如果未设置信号SIGALARM的处理函数,那么alarm()默认处理终止进程。

(4)函数返回值:如果在seconds秒内再次调用了alarm函数设置了新的闹钟,则后面定时器的设置将覆盖前面的设置,即之前设置的秒数被新的闹钟时间取代;当参数seconds为0时,之前设置的定时器闹钟将被取消,并将剩下的时间返回。

关于alarm函数的测试文章地址

  1. 定时器程序

在linux c编程中,setitimer是一个比较常用的函数,可用来实现延时和定时的功能,网上有各种零零散散的用法说明,都只提到了个别用法,今天抽空实践整理了一份比较详细的:

使用时需要引入的头文件:

#include <sys/time.h>
setitimer函数原型:
int setitimer(int which, const struct itimerval *new_value,
            struct itimerval *old_value);

其中which参数表示类型,可选的值有:

ITIMER_REAL:以系统真实的时间来计算,它送出SIGALRM信号。

ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。

ITIMER_PROF:以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。

紧接着的new_value和old_value均为itimerval结构体,先看一下itimerval结构体定义:

struct itimerval {
    
    
    struct timeval it_interval; /* next value */
    struct timeval it_value;    /* current value */
};

struct timeval {
    
    
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

itimeval又是由两个timeval结构体组成,timeval包含tv_sec和tv_usec两部分,其中tv_se为秒,tv_usec为微秒(即1/1000000秒)

其中的new_value参数用来对计时器进行设置,it_interval为计时间隔,it_value为延时时长,下面例子中表示的是在setitimer方法调用成功后,延时1微秒便触发一次SIGALRM信号,以后每隔200毫秒触发一次SIGALRM信号。

settimer工作机制是,先对it_value倒计时,当it_value为零时触发信号,然后重置为it_interval,继续对it_value倒计时,一直这样循环下去。

基于此机制,setitimer既可以用来延时执行,也可定时执行。

如果it_interval为零,只会延时,不会定时(也就是说只会触发一次信号)。

old_value参数,通常用不上,设置为NULL,它是用来存储上一次setitimer调用时设置的new_value值。

Guess you like

Origin blog.csdn.net/qq_42518941/article/details/121847538