Linux系统调用五、fcntl函数与非阻塞

简介: Linux系统调用五、fcntl函数与非阻塞

🚀1. 阻塞与非阻塞概念

阻塞可能会发生在read()函数读取设备、读取管道或读取网络的时候,因为某种情况需要等待,而不会立即返回,叫做阻塞。下面通过read()读设备来演示,比如读输入输出设备 /dev/tty 。

我们先写一个测试函数来看一下阻塞的效果,让read()函数读取标准输入输出设备tty的内容,如果标准输入输出没有内容的话,read()函数就会被阻塞,直到tty有内容了,才会继续执行。

/************************************************************
  >File Name  : read_tty.c
  >Author     : QQ
  >Company    : QQ
  >Create Time: 2022年05月13日 星期五 22时22分00秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
  int fd = open("/dev/tty", O_RDONLY);
  char buf[100];
  memset(buf, 0, sizeof(buf));
  while(1)
  {
    int ret = read(fd, buf, sizeof(buf));
    printf("read return : %d\n", ret);
    if(ret > 0)
    {
      printf("buf data : %s\n", buf); 
    } 
    printf("test : Because of blocking, not looping.\n");
  }
  close(fd);
  return 0;
}

这里的printf(“test…”)这句话主要是为了证明程序在阻塞,而不是在无限循环,假如程序暂停时没有打印这句话,说明程序还没有执行到这句话。下面编译运行一下,看一下效果。

通过运行结果可以看到,程序会卡住不动,直到我们在标准输入输出设备上输入内容,程序才会执行一个循环,并把我们输入的内容读出来。这就是阻塞,read()因为tty为空读不到内容,所以阻塞等待tty有内容。在这种状态下可以按ctrl+c来发信号退出。

这就是阻塞的效果,阻塞时整个程序卡在那不动,其实是很浪费资源的,下面我们看一下非阻塞的效果。下面先对之前的程序进行改动一下,通过open()函数的O_NONBLOCK参数来实现非阻塞打开文件。

int main()
{
  int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK); /*加O_NONBLOCK参数实现非阻塞*/
  char buf[100];
  memset(buf, 0, sizeof(buf));
  while(1)
  {
    int ret = read(fd, buf, sizeof(buf));
    if(ret < 0)
    {
      perror("read err: "); /*打印出错信息,read函数返回失败的时候会设置errno*/
    }
    printf("read return : %d\n", ret);
    if(ret > 0)
    {
      printf("buf data : %s\n", buf); 
    } 
    printf("test : Because of blocking, not looping.\n");
    sleep(2); /*睡眠2秒,不然的话会一直刷屏*/
  }
  close(fd);
  return 0;
}

非阻塞的情况下,如果read()函数读取不到内容,那么它不会阻塞在原地,而是会返回失败-1并设置errno 。因为是非阻塞,所以函数体内的循环会一直执行无限循环,所以要加一个睡眠函数,防止刷屏。

执行后可以看到,由于是非阻塞的方式打开tty设备,所以程序一直在循环执行,如果我们不向tty标准输入输出设备输入内容的话,read()会返回-1,并打印出一条出错信息 “Resource temporarily unavailable” ,然后继续执行下一次循环(如果是阻塞的方式打开文件,read()函数读取不到内容会暂停),如果我们输入内容,read()函数会把我们输入的内容读取出来并返回读取的字节数。

🚀2. fcntl函数设置非阻塞

  • 包含头文件
#include <unistd.h>
#include <fcntl.h>
  • 函数原型
int fcntl(int fd, int cmd, ... /* arg */ );
  • 函数功能
    fcntl() performs one of the operations described below on the open file descriptor fd. The operation is determined by cmd. 打开一个文件描述符,操作由cmd来决定。这个函数功能还是很多的,可以通过 man 2 fcntl 来查看。
  • 函数参数它是一个可变参数的函数,… /* arg */ 的内容取决于cmd,比较常用的两个如下
  • 获取标志:F_GETFL (void) Read the file status flags; arg is ignored. 也就是说,如果cmd选择F_GETFL的话,后面参数可以为void,也就是没有第三个参数
  • 设置标志:F_SETFL (long) Set the file status flags to the value specified by arg. File access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation flags (i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored. On Linux this command can only change the O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK flags. 这个的话,如果cmd是F_SETFL,那么后边参数应该是long类型的
  • 函数返回值
    F_GETFL Value of flags. 如果我们使用的cmd参数是F_GETFL 那么就会把获取的标志返回出来。实际上,fcntl()函数的返回值也是由cmd参数来决定的,这只是比较常用的一个,更多的返回值可以在man手册查询。

下面我们通过实例来说明这个函数的用法,接上一节的话题,我们可以不在open()打开文件的时候设置非阻塞,而是在程序中使用fcntl()函数来设置非阻塞参数,具体代码如下。

int main()
{
  int fd = open("/dev/tty", O_RDONLY);
  /*第一次调用,使用F_GETFL来获取标志,并通过返回值返回*/
  int flag = fcntl(fd, F_GETFL);
  /*把标志重新设置,通过或运算置非阻塞位*/
  flag |= O_NONBLOCK;
  /*第二次调用,使用F_SETFL参数来设置标志位,把上面修改好的标志在设置回文件*/
  fcntl(fd, F_SETFL, flag);
  char buf[100];
  memset(buf, 0, sizeof(buf));
  while(1)
  {
    int ret = read(fd, buf, sizeof(buf));
    if(ret < 0)
    {
      perror("read err: ");
    }
    printf("read return : %d\n", ret);
    if(ret > 0)
    {
      printf("buf data : %s\n", buf); 
    } 
    printf("test : Because of blocking, not looping.\n");
    sleep(2); 
  }
  close(fd);
  return 0;
}

在这里通过fcntl()函数三行代码也实现了非阻塞打开文件的效果,和上面的open()直接加参数O_NONBLOCK效果是一样的。

这个函数功能是非常多的,这里只介绍用于设置非阻塞这一个功能,其他功能在后面用到时介绍。



相关文章
|
8天前
|
Linux
【Linux】System V信号量详解以及semget()、semctl()和semop()函数讲解
System V信号量的概念及其在Linux中的使用,包括 `semget()`、`semctl()`和 `semop()`函数的具体使用方法。通过实际代码示例,演示了如何创建、初始化和使用信号量进行进程间同步。掌握这些知识,可以有效解决多进程编程中的同步问题,提高程序的可靠性和稳定性。
47 19
|
10天前
|
Linux Android开发 开发者
linux m、mm、mmm函数和make的区别
通过理解和合理使用这些命令,可以更高效地进行项目构建和管理,特别是在复杂的 Android 开发环境中。
40 18
|
18天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
83 13
|
3月前
|
网络协议 Linux 调度
深入探索Linux操作系统的心脏:内核与系统调用####
本文旨在揭开Linux操作系统中最为核心的部分——内核与系统调用的神秘面纱,通过生动形象的语言和比喻,让读者仿佛踏上了一段奇妙的旅程,从宏观到微观,逐步深入了解这两个关键组件如何协同工作,支撑起整个操作系统的运行。不同于传统的技术解析,本文将以故事化的方式,带领读者领略Linux内核的精妙设计与系统调用的魅力所在,即便是对技术细节不甚了解的读者也能轻松享受这次知识之旅。 ####
|
3月前
|
缓存 算法 安全
深入理解Linux操作系统的心脏:内核与系统调用####
【10月更文挑战第20天】 本文将带你探索Linux操作系统的核心——其强大的内核和高效的系统调用机制。通过深入浅出的解释,我们将揭示这些技术是如何协同工作以支撑起整个系统的运行,同时也会触及一些常见的误解和背后的哲学思想。无论你是开发者、系统管理员还是普通用户,了解这些基础知识都将有助于你更好地利用Linux的强大功能。 ####
52 1
|
4月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
176 6
|
4月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
188 3
|
4月前
|
Linux
在Linux内核中根据函数指针输出函数名称
在Linux内核中根据函数指针输出函数名称
|
4月前
|
存储 Linux 程序员
Linux中的主要系统调用
【9月更文挑战第11天】在Linux操作系统中,通过系统调用`fork`创建新进程,子进程继承父进程的数据结构与代码,但可通过`execve`执行不同程序。`fork`返回值区分父子进程,`waitpid`让父进程等待子进程结束。
|
4月前
|
Linux 开发者 Python
从Windows到Linux,Python系统调用如何让代码飞翔🚀
【9月更文挑战第10天】在编程领域,跨越不同操作系统的障碍是常见挑战。Python凭借其“编写一次,到处运行”的理念,显著简化了这一过程。通过os、subprocess、shutil等标准库模块,Python提供了统一的接口,自动处理底层差异,使代码在Windows和Linux上无缝运行。例如,`open`函数在不同系统中以相同方式操作文件,而`subprocess`模块则能一致地执行系统命令。此外,第三方库如psutil进一步增强了跨平台能力,使开发者能够轻松编写高效且易维护的代码。借助Python的强大系统调用功能,跨平台编程变得简单高效。
73 0