Linux高级IO

Linux高级IO

五种IO模型
  • 阻塞IO:内核在准备好数据之前,系统调用会一直等待,直到内核准备好数据,再将数据报从内核拷贝到用户空间,系统调用 才会成功返回。所有的套接字默认都是阻塞方式,如:recvfrom /sendto
  • 非阻塞IO:若内核未准备好数据,系统调用会直接返回,并返回EWOULDBLOCK错误码。这就需要程序员反复尝试读写文件描述符,也称这种方式为“轮询”,不足之处正在于浪费cpu,因为大多数时间是无数据可读的,但仍花费时间不断执行read进行系统调用,多任务系统中应该避免使用。
  • 信号驱动IO:用信号处理函数sigaction对SIGIO信号进行处理,一旦内核准备好了数据,SIGIO信号会通知应用进程进行IO操作。这种方式一旦信号被触发,进程中的所有线程都要被挂起,对于服务器进程来说不友好,当信号到来,所有处理客户端请求的线程都要被挂起,都在等待数据从内核拷贝到用户空间,显然不可以。
  • IO多路转接:先构造一张有关文件描述符的列表,然后调用一个函数,当这些文件描述符中有一个处于就绪状态,该函数就返回,返回时告诉进程哪些文件描述符准备好了。其本质是它可以等待多个文件描述符的就绪状态。这种方式现已被广泛使用。是一种有效提高时间利用率的手段(类似医生看病)。select,poll,epoll这三个函数能够实现IO多路复用。IO多路复用适用于非常多的客户端来访问服务器,但只有几个处于活跃状态(QQ服务器)
  • 异步IO:内核拷贝完数据后,才会通知应用进程,

任何IO过程都包括两个步骤:1.等待;2.拷贝数据;一般来说,等待的时间都会远远大于拷贝数据的时间,要优化IO时间,提高性能,就要从数据等待的时间入手。凡是提到给一个程序进行性能优化,应该先寻找性能瓶颈。这里我们要优化等待时间,可将系统调用函数设置成非阻塞,若无数据直接返回,省去了等待数据的时间,两次调用之间程序可以做其他事情,时间并没有被浪费。信号驱动IO也是如此,在数据未准备好时,可以一直做其他事情。直到数据就绪,才会触发信号进行IO操作。

同步和异步:描述的是调用者和被调用者之间的行为

  • 同步是指由调用者主动等待调用的结果。当调用者发起一个调用,未得到结果之前调用一直不返回,直到得到返回值,调用才会返回。就好比我们去吃饭,点餐后一直在前台等待,直到饭做好后我们才端走去吃。
  • 异步是指调用者发出调用之后未收到返回结果就直接返回了,它的返回结果是被调用者通过状态、通知来将结果反馈给调用者。再如我们出去吃饭,点餐后我们就找个地坐下,当饭好了之后,服务员会为我们端过来。

下面的同步与上面的同步完全不是一个概念

同步和互斥

  • 同步是指为完成某个任务而建立两个或多个线程或进程,他们的运行必须严格按照某种先后次序来进行,比如A任务的运行依赖于B任务产生的数据。

  • 互斥是指散布在不同任务之间的若干程序片段,当某个任务运行其中一个程序片段时,其他任务就不能运行他们之中的任何一个程序片段,只能等该任务运行完这个程序片段后才可以运行。比如,一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。

-

阻塞与非阻塞:他们关注的是程序在等待调用结果时的状态。

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起,只有在得到结果之后才会返回
  • 非阻塞调用是指未得到结果就直接返回,不会阻塞当前线程

其他高级IO还有:非阻塞IO,记录锁,系统V流机制,IO多路转接,readv和writev函数及存储映射IO(mmap)。

实现非阻塞IO

对一个给定的文件描述符,我们有两种方式将其指定为非阻塞IO:
(1).若调用open获得文件描述符,则可指定O_NONBLOCK标志。
(2).对一个已打开的文件描述符,可调用fcntl函数,由该函数打开O_NONBLOCK文件状态标识。

fcntl函数

这里写图片描述
返回值:成功依赖cmd,出错则返回-1。

该函数可改变已打开文件的的性质
传入cmd的值不同,后面追加的参数也不同,第三个参数总是一个整数。

它有五种功能:

  • 复制一个现有的文件描述符(cmd=F_DUPFD)
  • 获得/设置文件描述符标记(cmd=FGETFD/FSETFD)
  • 获得/设置文件描状态标记(cmd=FGETFL/FSETFL)
  • 获得/设置异步IO所有权(cmd=FGETOWN/FSETOWN)
  • 获得/设置记录锁(cmd=FGETLK/FSETLK)

我们用到的是第三种功能获得/设置文件描状态标记(cmd=FGETFL/FSETFL),就可以将一个文件描述符设置为非阻塞。
通过FGETFL获得的文件状态标识如下表:
这里写图片描述
FSETFL是将文件状态标志设置为第三个参数的值,除前三种状态之外,其他的 状态标志都可更改。

轮询方式读取标准输入:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

//通过这个函数将文件描述符设置为非阻塞
int SetNoBlock(int fd)
{
  int flag=fcntl(fd,F_GETFL);
  if(flag<0)
  {
    perror("fcntl");
    return -1;
  }
  int ret=fcntl(fd,F_SETFL,flag|O_NONBLOCK);
  if(ret<0)
  {
    perror("fcntl");
    return -1;
  }
  return 0;
}

int main()
{
  SetNoBlock(0);
  //从标准输入尝试读数据
  while(1)
  {
    usleep(100000);
    char buf[1024]={0};
    printf(">");
    fflush(stdout);
    ssize_t read_size=read(0,buf,sizeof(buf)-1);
    if(read_size<0)
    {
      perror("read");
      continue;
    }
    if(read_size==0)
    {
      printf("read done!\n");
      return 0;
    }
    buf[read_size]='\0';
    printf("buf=%s\n",buf);
  }
  return 0;
}

dup和dup2函数

这里写图片描述
返回值:若成功返回新的文件描述符,失败返回-1.

这两个函数在man手册中是这样进行描述的:
这里写图片描述
由dup返回的新文件描述符一定是当前可用文件描述符中的最小值。
而dup2可以指定新文件描述符的数值。若newfd已经打开,则先将其关闭。若newfd和oldfd相等,则不关闭直接返回newfd。

我们还可以用上面提到的fcntl函数进行文件描述符的复制,利用fcntl的第一种功能:
dup(oldfd)就相当于fcntl(oldfd,F_DUPFD,0)
dup2(oldfd,newfd)相当于close(newfd);fcntl(oldfd,F_DUPFD,newfd);

在第二种情况中,dup2并不完全等价于close加fcntl,是因为:
1.dup2是一个原子操作,而close和fcntl是两个函数调用,当执行完close后,有可能某些信号被触发,进程去执行信号处理函数,这时就有可能对文件描述符进行修改。
2.dup2和fcntl有某些不同的error

使用dup将标准输出重定向到文件中

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
  int fd=open("./1.txt",O_CREAT|O_RDWR);
  if(fd<0)
  {
    perror("open");
    return 1;
  }
  close(1);
  int new_fd=dup(fd);
  if(new_fd!=1)
  {
    perror("dup");
    return 1;
  }
  printf("new_fd %d\n",new_fd);
  close(fd);

  while(1)
  {
    char buf[1024]={0};
    ssize_t read_size=read(0,buf,sizeof(buf)-1);
    if(read_size<0)
    {
      perror("read");
      continue;
    }
    printf("%s\n",buf);
    fflush(stdout);
  }
  close(new_fd);
  return 0;
}

使用dup2将标准输出重定向到文件中

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
  int fd=open("./2.txt",O_CREAT|O_RDWR,0644);
  if(fd<0)
  {
    perror("open");
    return 1;
  }
  close(1);
  int new_fd=dup2(fd,1);
  if(new_fd<0)
  {
    perror("dup2");
    return 1;
  }
  printf("new_fd %d\n",new_fd);
  while(1)
  {
    char buf[1024]={0};
    ssize_t read_size=read(0,buf,sizeof(buf)-1);
    if(read_size<0)
    {
      perror("read");
      continue;
    }
    buf[read_size]='\0';
    printf("%s\n",buf);
    fflush(stdout);
  }
  close(new_fd);
  return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值