Linux系统编程之通用I/O模型
简介
上次我们简单介绍了一下Linux系统编程的环境搭建,这篇开始正式走进Linux系统编程的篇章。我们之前介绍操作系统的时候说过,无非是为了更好控制硬件才选择开发操作系统,人与硬件的交互无非就是人输入,硬件输出想要的结果,但是我们知道硬件有很多类型,什么硬盘,网卡,终端,串口,管道等等它们底层硬件各不相同,要实现读写操作也十分麻烦,而且我们都知道学习Linux,首要就是要有一切皆文件的思想,以这个思想为基础,为了解决硬件差异造成读写不便的问题,通用I/O应运而生。
通用I/O
我们在这篇内,主要介绍这几个通用I/O的用法,不讲底层具体实现,主要为了让大家有一个基本写Linux应用代码框架。通用I/O有五个open(),read(),write(),close(),lseek()。
下面给出五个函数的参数情况和返回值类型
int open(char *pathname,int flags,mode_t mode);
ssize_t read(int fd,void *buffer,size_t count);
ssize_t write(int fd,void *buffer,size_t count);
int close(int fd);
off_t lseek(int fd;off_t offset;int whence);
是不是看的很迷糊,我们一点一点来分析。首先我们可以看到一个变量出现的特别多,就是fd,这个整形变量fd代表什么呢,它叫文件描述符,是一个小整数,你可以把它当做一个号码牌,每次做一件事的时候都需要一个这种号码牌,用来处理本次操作信息,你的每个进程启动都会自带三个这种号码牌0,1,2,0代表通用输入,1代表通用输出,2代表通用错误。此后如果想要做其他事就需要自己申请文件描述符啦。
同时有一些我们没见过的变量类型,这里简单说明一下,比如mode_t就是专门为权限mode设定的变量类型,size_t是有符号整形,ssize_t是无符号整形,off_t则是文件偏移专门的一个整形类型(64位系统一般是long long,以操作超大文件),这些变量名不仅方便编程者们记忆,而且专门设计的变量名可以有效防止因为整形类型不合适导致范围溢出的现象。
好了,前置知识讲完了,该具体讲讲这几个东西有什么用了,首先,是它们的基础open,它返回一个int类型的fd,对就是我们刚刚学到的fd,它就是我们申请文件描述符的函数,它的作用是根据flag参数打开pathname这个文件,如果设置了不存在就创建的话要写权限mode(这里参数比较多,一般都是现查的,注意参数间用按位或’|'隔开呦),这里说到权限其实非常重要,它其实有个公式,实际权限=设置权限-umask,umask就是权限屏蔽数,我们知道一个文件权限其实是一个三位八进制数分别代表拥有者,拥有者group,和陌生人三部分人的权限,umask就是在你设置权限的基础上给你屏蔽掉一部分权限,如果你是普通用户这个数默认002,如果是root则默认022,举个例子,你比如权限是666,普通用户创建后它会变成664也就是陌生人只读。总而言之呢,这个open就是按照参数要求打开文件,并给它一个号码牌,read,write顾名思义了,我们要操作刚刚得到的号码牌,用这个号码牌叫操作了,read就是读count个字节数据存放到缓冲区里,write反过来就是从count里写进buffer个字节数据。close呢就是释放掉你申请的号码牌,你想想你去餐厅吃完饭牌子总要还给人家嘛。最后再来唠唠这个lseek,read,和write,其实是在这个文件的某个位置进行读写,我们叫这个东西是偏移量,lseek是设置这个偏移量的,offset是偏移量,whence是相对位置,你可以设置开头,结尾或者相对当前位置都可以(这里插入一条知识点,如果你偏移量的末尾超出了文件末尾,你可以把这个当成溢出吧,不过这是合法的呦,而且还非常好用,我们叫块东西是空洞,虽然你创建了它但是在你对它进行读写的时候它不占用磁盘,只是逻辑上占用,类似于预定吧,你后期如果要学到更新代码,制作镜像等要留足空间的操作它可是个好帮手)。不过今天我们主要讲前四个的用法,下面用一个模拟Linux代码cp的copy.c来简单讲解一下。
示例代码copy.c
#include <fcntl.h>
#include <sys/stat.h>
#include "tlpi_hdr.h"
#define BUF_SIZE 1024
int main(int argc,char*argv[])
{
int inputFd,outputFd,openflags;//记录输入输出的fd,和flag参数
mode_t filePerms;//文件权限
ssize_t numread;//读取字节数
char *buffer[BUF_SIZE];
if(argc!=3||strcmp(argv[1],"--help") ==0)
usageErr("%s old_file new_file\n",argv[0]);
inputFd = open(argv[1],RDONLY);//申请被读文件的fd
if(inputFd == -1)
errExit("opening file %s\n",argv[1]);
openFlags = O_CREAT | O_WRONLY | O_TRUNC;//参数及权限设置
filePerms = S_IRUSR | S_IWUSR | S_IRGRP| S_IWGRP | S_IROTH |S_IWOTH; /* rw-rw-rw- */
outputFd = open(argv[2],openFlags,filePerms);//申请目标文件的fd
if(outputFd == -1)
errExit("opening file %s\n",argv[2]);
while((numread = read(inputFd,buffer,BUF_SIZE)>0)//写入循环
if(write(outputFd,buffer,BUF_SIZE) != numread)
fatal("couldn't write whole buffer");
if(numread == -1)
errExit("read");
if(close(inputFd) == -1)
errExit("close input");
if(close(outputFd) == -1)//释放fd
errExit("close output");
exit(EXIT_SUCCESS);
}
注:文中用的库下载了TLPI的官方源码,读者想要复用代码需自行下载编译https://www.man7.org/tlpi/code/download/tlpi‑240828‑dist.tar.gz
接下来我们需要对我们写的代码进行编译
gcc copy.c -o copy -ltlpi
用以上代码编译后就可以用该代码模拟cp了。
编程思路
总结一下,我们要完成一个功能的代码编写,需要有以下步骤,1,校验命令行参数,2,打开文件,并设置权限相关信息,3,工作区(主要不同点),4,释放文件资源,5,退出,这五步,其实呀只有第三步是我们需要修改的地方,其他东西都是框架性质的东西,用多了自然会很熟练了。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)