【嵌入式应用开发】Linux文件IO常用的四种访问方式

Linux系统 中,一切都可以看成"文件"<通常包含:普通文件、驱动文件、网络通信文件 等等>,系统中所有的操作都可以通过文件I/O实现,因此,掌握文件常用接口很有必要。

0.前言

屏幕前的你如果懂得main函数传递参数机制,可以跳过本部分直接看后续内容。

在正式内容开始前,小编想讲述Linux中main函数传参的机制。

  • linux中main函数抢先看:
C 复制代码
int main(int argc , char* argv[])
  • 函数参数说明:

      • int arc:整型,表示传入参数的个数;
    • char* argv[]:字符型数组,表示实际传入的参数,该参数还存在另一种定义方式,即char argv[][];
  • 实际使用样例:

  • 样例函数

c 复制代码
#include <stdio.h>

int main(int argc,char*argv[])
{
  	//打印参数个数
  	printf("argc:%d\n",argc);
  	//遍历参数并且打印
  	printf("argv:");
  	for(int i=0;i<argc;i++)
  	{
  		printf("%s ",argv[i]);
  	}
  	printf("\n");
  	return 0;
}
  • 运行结果
  • 这里需要注意的是运行命令*./test也是传入main函数的参数**。

1.Linux系统中文件来源

  • 来源于磁盘、flash、SD卡等,这些文件是真实存在的,通常以某种格式存储在某个设备上;
  • 内核产生的虚拟文件系统,这些文件并不是真实存在的,但是支持真实文件的操作;
  • 特殊文件,特殊文件可以是字符设备文件、块设备文件等;

2.访问方式

文件访问函数通常可分为:通用的I/O函数与非通用的I/O访问函数;

  • 通用访问I/O的函数
    • open:打开文件;
    • read:读取文件;
    • write:写入文件;
    • lseek:定位文件内容;
    • close:关闭文件;
  • 非通用访问文件I/O的函数:
    • ioctl:设备控制接口函数
    • mmap:文件映射函数;

查询方式访问

查询方式,设备文件以非阻塞方式打开后,不断发送读取文件请求的一种方式。例如:当你找人有急事时,就会一直尝试联系某人,这就类似于文件的查询方式。

由于查询方式是不断发送读取请求,因此它访问次数非常频繁占用CPU资源相对较多

核心代码

c 复制代码
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>

int main(int argc, char const *argv[])
{
  int fd = 0;
  int err = 0;
  struct input_id id;
  unsigned int evbit[2];
  int len,res;
  struct input_event event;
  
  //判断参数个数是不是2 一个运行参数  一个文件设备
  if (argc != 2)
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}
	/* 打开驱动程序 */
	fd = open(argv[1], O_RDWR | O_NONBLOCK);
	//打开失败 返回
    if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}
  //不断查询方式 非阻塞
  while(1)
  {
  	//读取数据并判断数据大小是否正确
    len = read(fd,&event,sizeof(event));
    if(len == sizeof(event))
    {
      printf("get event:type=0x%x code=0x%x value=0x%x\n",event.type,event.code,event.value);
    }
    else
    {
      printf("read err\n");
    }
  }
}

休眠唤醒方式访问

休眠唤醒方式,这里是指:首先文件以阻塞方式打开,当读取到文件数据时,阻塞态就会转换为运行态,读取出数据会直接返回,进行下一步操作。比如:大家在学校上课时(这里将上课作为阻塞态,下课作为运行态),一旦上课铃声响起,咱就会进入阻塞态上课;而下课铃声想起后,大家就开始运行了,进入运行态,直接起飞。:nerd_face::nerd_face::nerd_face:

休眠唤醒方式,占用CPU资源相对较少请求次数相对减少;但是,一旦没有请求到数据就会一直进入阻塞,不会干其他活,这就大大限制了进程的运行状态。

核心代码

c 复制代码
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>


int main(int argc, char **argv)
{
	int fd;
	int err;
	int len;
	int i;
	unsigned char byte;
	int bit;
	unsigned int evbit[2];
	struct input_event event;
	//判断参数个数是不是2 一个运行参数  一个文件设备
	if (argc != 2)
	{
		printf("Usage: %s <dev> err\n", argv[0]);
		return -1;
	}
	/* 打开驱动程序 */
	fd = open(argv[1], O_RDWR);
	//打开失败 返回
	if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}
	//不断查询 阻塞
	while (1)
	{
		//读取数据 并且判断数据大小是否正确
		len = read(fd, &event, sizeof(event));
		if (len == sizeof(event))
		{
			printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
		}
		else
		{
			printf("read err %d\n", len);
		}
	}
	return 0;
}

以POLL/SELECT方式访问

POLL/SELECT方式访问,首先文件以非阻塞方式打开 ,再使用poll()函数设置一个固定的超时时间。在这段超时时间内,如果文件能够读出或写入 时就会立即返回;否则,时间超时后返回错误

  • 函数原型
c 复制代码
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • 参数说明

    c 复制代码
    struct pollfd {
        int   fd;         /* file descriptor,目标文件*/
        short events;     /* requested events,操作事件*/
        short revents;    /* returned events,超时事件返回值*/
    };
    c 复制代码
    nfds_t nfds;          /*事件的个数*/
    c 复制代码
    int timeout;		  /*设置超时时间*/

核心代码

c 复制代码
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>

int main(int argc, char const *argv[])
{
  int fd = 0;
  int err = 0;
  unsigned int evbit[2];
  int len,res;
  struct input_event event2;
  struct pollfd fds[1];
  nfds_t nfds = 1;
    
  //poll-seelct  设置一个固定的时间查询输入事件
  if(argc!=2)
  {
    printf("err\n");
    return 0-1;
  }
  //以非阻塞的方式打开
  fd = open(argv[1],O_RDWR|O_NONBLOCK);
  if(fd < 0 )
  {
      printf("open %s err\n",argv[1]);
      return -1;
  }

  while(1)
  {
  //设置监听事件相关参数
    fds[0].fd = fd;
    fds[0].events = POLLIN;
    fds[0].revents = 0;
    res = poll(fds,nfds,5000);
      
    //返回值有效
    if(res > 0)
    {
      //判断事件方式是否数输入方式
      if(fds[0].revents == POLLIN)
      {
        //数据读取完成后返回
        while(read(fd,&event,sizeof(event))==sizeof(event))
        {
          printf("get event:type=0x%x code=0x%x value=0x%x\n",event.type,event.code,event.value);
        }
      }
    }
    else if(res == 0)
    {
      printf("time out\n");
    }
    else
    {
      printf("poll err\n");
    }

  }
  return 0;
}

异步通知方式

异步通知方式,文件是以非阻塞方式打开的,在初始化设置完成后,当没有读取到数据时,程序可以干其他的事,读取数据操作并不影响程序的其他操作。看到这儿,大家是不是觉得这种处理方式很类似于单片机的中断处理函数。:smile_cat::smile_cat::smile_cat:

异步通知方式,使用了信号量,信号量在此处的作用是通知驱动程序是否能够成功读取数据,能够成功读取数据后,驱动就读取数据;否则就将其闲置在一旁。

代码

c 复制代码
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>

int fd = 0;
struct input_event event;

void my_sig_handler(int sing)
{
  while(read(fd,&event,sizeof(event))==sizeof(event))
  {
    printf("get event:type=0x%x code=0x%x value=0x%x\n",
    			event.type,event.code,event.value);
  }
}

int main(int argc, char const *argv[])
{
  int fd2 = 0;
  int err = 0;
  struct input_id id;
  unsigned int evbit[2];
  int len,res;
  struct input_event event2;

  struct pollfd fds[2];
  nfds_t nfds = 2;
  unsigned int count = 0,flags;
  //判断参数个数是不是2 一个运行参数  一个文件设备
  if (argc != 2)
  {
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
  }

  /* 注册信号处理函数 */
  signal(SIGIO, my_sig_handler);
  /* 打开驱动程序 */
  fd = open(argv[1], O_RDWR | O_NONBLOCK);
  if (fd < 0)
  {
        printf("open %s err\n", argv[1]);
        return -1;
  }

  /* 把APP的进程号告诉驱动程序 */
  fcntl(fd, F_SETOWN, getpid());
  /* 使能"异步通知" */
  flags = fcntl(fd, F_GETFL);
  fcntl(fd, F_SETFL, flags | FASYNC);
    
  while(1)
  {
  	//主进程干的活
    printf("count:%d\n",count++);
    sleep(2);
  }
  return 0;
}

3.小结

尽管,上文的四种文件访问方式各有优劣,但只要在合适的时机使用就能够发挥其最大效率!

相关推荐
jjyangyou15 小时前
物联网核心安全系列——物联网安全需求
物联网·算法·安全·嵌入式·产品经理·硬件·产品设计
WZF-Sang1 天前
Linux—进程学习-01
linux·服务器·数据库·学习·操作系统·vim·进程
憧憬一下1 天前
Pinctrl子系统中Pincontroller和client驱动程序的编写
arm开发·嵌入式·c/c++·linux驱动开发
蓝天居士1 天前
ES8388 —— 带耳机放大器的低功耗立体声音频编解码器(4)
嵌入式·音频·es8388
田三番1 天前
使用 vscode 简单配置 ESP32 连接 Wi-Fi 每日定时发送 HTTP 和 HTTPS 请求
单片机·物联网·http·https·嵌入式·esp32·sntp
启明智显2 天前
AI笔筒操作说明及应用场景
人工智能·嵌入式硬件·嵌入式·ai大模型·启明智显·esp32-s3
Goboy2 天前
0帧起步:3分钟打造个人博客,让技术成长与职业发展齐头并进
程序员·开源·操作系统
结衣结衣.2 天前
【Linux】Linux管道揭秘:匿名管道如何连接进程世界
linux·运维·c语言·数据库·操作系统
FreakStudio2 天前
全网最适合入门的面向对象编程教程:58 Python字符串与序列化-序列化Web对象的定义与实现
python·单片机·嵌入式·面向对象·电子diy
OpenAnolis小助手2 天前
龙蜥副理事长张东:加速推进 AI+OS 深度融合,打造最 AI 的服务器操作系统
ai·开源·操作系统·龙蜥社区·服务器操作系统·anolis os