目录
前言
🎬 个人主页:@ChenPi
🐻推荐专栏1: 《C++_@ChenPi的博客-CSDN博客》✨✨✨
🔥 推荐专栏2: 《Linux C应用编程(概念类)_@ChenPi的博客-CSDN博客》✨✨✨
🛸推荐专栏3: 《链表_@ChenPi的博客-CSDN博客 》 ✨✨✨
🌺本篇简介 : 本期我们学习一下关于文件操作的系统调用,Linux一切皆文件,所以这章还是比较重要的,这章主要搞定三个API,open,read,write
一 文件描述符的概念
/* 打开源文件 src_file(只读方式) */
fd1 = open("./src_file", O_RDONLY);
/* 打开目标文件 dest_file(只写方式) */
fd2 = open("./dest_file", O_WRONLY);
- 当调用 open 函数打开一个现有文件或创建一个新文件时,内核会向进程返回一个文件描述符,用于指 代被打开的文件。
- 所有执行 IO 操作的系统调用都是通过文件描述符来索引到对应的文件,当调用 read/write 函数进行文件读写时,会将文件描述符传送给 read/write 函数
- 所以在代码中,fd1 就是源文件 src_file 被打开时所对应的文件描述符,而 fd2 则是目标文件 dest_file 被打开时所对应的文 件描述符。
- 什么是文件描述符? 文件句柄、一个非负整数、与对应的文件相绑定
- 文件描述符如何分配 每次分配一个没有被使用的最小非负整数作为文件描述符
- 标准输入、标准输出和标准错误 0、1、2
一个进程可以打开多个文件,但是在 Linux 系统中,一个进程可以打开的文件数是有限制,并不是可以 无限制打开很多的文件,我们可以通过 ulimit 命令来查看进 程可打开的最大文件数,用法如下所示: ulimit -n
该最大值默认情况下是 1024,也就意味着一个进程最多可以打开 1024 个文件
二 打开文件 open()、关闭文件 close()
1.创建(creat)
函数原型:
int creat(const char *pathname,mode_t mode); //pathname:指向文件路径的指针
int creat("/home/abl/demo",S_IRWXU);//绝对路径:/home/abl/demo
int creat("./demo",S_IRWXU);//当前路径:./demo
mode:创建模式(可读可写可执行)系统自带四个宏
- 可执行(1):S_IXUSR
- 可写(2):S_IWUSRL
- 可读(4):S_IRUSR
- 可读、可写、可执行(7):S_IRWXU
返回值为文件描述符
成功返回一个非负整数
失败返回-1
int fd_file1;
int creat("/home/abl/demo",S_IRWXU);//fd_file1为creat返回的文件描述符
2.打开(open)
函数原型:
int open( const char *pathname,int flags);//pathname:指向文件路径的指针;flags:文件权限
int open( const char *pathname,int flags,mode_t mode);
int creat( const char *pathname,mode_t mode);
pathname:指向文件路径的指针
mode:创建模式(可读可写可执行)
flags:文件权限
2.1 mod参数选择
第一类:(只能3选1)
- O_RDONLY 只读打开
- O_WRONLY 只写打开
- O_RDWR 可读可写打开
第二类:(可以多选)
1、O_CREAT若文件不存在则创建它,使用此选项时,需要同时说明第三个参数mode,用其说明该新文件的存取许可权限。
fd_file1=open("./file1",O_RDWR|O_CREAT,0600);
2、O_EXCL如果同时指定了O_CREAT,而文件已经存在,则出错(返回-1)。
fd_file1=open("./file1",O_RDWR|O_CREAT|O_EXCL,0600);
//fd_file1为open返回的文件描述符,此时若已经存在file1,open返回-1
3、O_APPEND每次写时都加到文件的尾端。
fd_file1=open("./file1",O_RDWR|O_APPEND);
4、O_TRUNC属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或只写成功打开,则将其长度截短为0。
fd_file1=open("./file1",O_RDWR|O_TRUNC);//此时可将原文件里的信息先删除后写入
5. O_NOFOLLOW 如果 pathname 参数指向的是一个符号链接, 将不对其进行解引用,直接返回错误。
2.2 flags:文件权限
- 可执行:0100
- 可写:0200
- 可读:0400
- 可读、写:0600
- 可读、可写、可执行:0700
- 一定是在flags中使用了O_CREAT标志,mode记录待创建的文件的访问权限
3.open 函数使用示例
(1)使用 open 函数打开一个已经存在的文件(例如当前目录下的 app.c 文件),使用只读方式打开:
int fd = open("./app.c", O_RDONLY) if (-1 == fd) return fd;
(2)使用 open 函数打开一个已经存在的文件(例如当前目录下的 app.c 文件),使用可读可写方式打开:
int fd = open("./app.c", O_RDWR) if (-1 == fd) return fd;
(3)使用 open 函数打开一个指定的文件(譬如/home/dengtao/hello),使用可读可写方式,如果该文件是 一个符号链接文件,则不对其进行解引用,直接返回错误:
int fd = open("/home/dengtao/hello", O_RDWR | O_NOFOLLOW); if (-1 == fd) return fd;
(4)使用 open 函数打开一个指定的文件(譬如/home/dengtao/hello),如果该文件不存在则创建该文件, 创建该文件时,将文件权限设置如下:文件所属者拥有读、写、执行权限;同组用户与其他用户只有读权限。使用可读可写方式打开:
int fd = open("/home/dengtao/hello", O_RDWR | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
if (-1 == fd)
return fd;
4. 关闭(close)
可调用 close 函数关闭一个已经打开的文件,其函数原型如下所示(可通过"man 2 close"查看):
#include <unistd.h>
int close(int fd);
首先使用 close 函数需要先包含 unistd.h 头文件,当我们对文件进行 IO 操作完成之后,后续不再对文件 进行操作时,需要将文件关闭。
函数参数和返回值含义如下: fd:文件描述符,需要关闭的文件所对应的文件描述符。
返回值:如果成功返回 0,如果失败则返回-1。
三 写文件 write()、读文件 read()
3.1 write 写文件
调用 write 函数可向打开的文件写入数据,其函数原型如下所示(可通过"man 2 write"查看):
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
首先使用 write 函数需要先包含 unistd.h 头文件。
- buf:指定写入数据对应的缓冲区。
- count:指定写入的字节数。
- 返回值:如果成功将返回写入的字节数(0 表示未写入任何字节),如果此数字小于 count 参数,这不 是错误,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回-1。
对于普通文件(我们一般操作的大部分文件都是普通文件,譬如常见的文本文件、二进制文件等),不 管是读操作还是写操作,一个很重要的问题是:从文件的哪个位置开始进行读写操作?也就是 IO 操作所对 应的位置偏移量,读写操作都是从文件的当前位置偏移量处开始,当然当前位置偏移量可以通过 lseek 系统 调用进行设置,关于此函数后面再讲;默认情况下当前位置偏移量一般是 0,也就是指向了文件起始位置, 当调用 read、write 函数读写操作完成之后,当前位置偏移量也会向后移动对应字节数,譬如当前位置偏移 量为 1000 个字节处,调用 write()写入或 read()读取 500 个字节之后,当前位置偏移量将会移动到 1500 个字 节处。
3.2 read 读文件
调用 read 函数可从打开的文件中读取数据,其函数原型如下所示(可通过"man 2 read"查看): #include ssize_t read(
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
首先使用 read 函数需要先包含 unistd.h 头文件。
函数参数和返回值含义如下:
- fd:文件描述符。与 write 函数的 fd 参数意义相同。
- buf:指定用于存储读取数据的缓冲区。
- count:指定需要读取的字节数。
- 返回值:如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节 数,也有可能会为 0,譬如进行读操作时,当前文件位置偏移量已经到了文件末尾。实际读取到的字节数少 于要求读取的字节数,譬如在到达文件末尾之前有 30 个字节数据,而要求读取 100 个字节,则 read 读取成 功只能返回 30;而下一次再调用 read 读,它将返回 0(文件末尾)。
四 文件读写位置偏移量。
4.1 lseek
对于每个打开的文件,系统都会记录它的读写位置偏移量,我们也把这个读写位置偏移量称为读写偏移 量,记录了文件当前的读写位置,当调用 read()或 write()函数对文件进行读写操作时,就会从当前读写位置 偏移量开始进行数据读写。
读写偏移量用于指示 read()或 write()函数操作时文件的起始位置,会以相对于文件头部的位置偏移量来 表示,文件第一个字节数据的位置偏移量为 0。
当打开文件时,会将读写偏移量设置为指向文件开始位置处,以后每次调用 read()、write()将自动对其 进行调整,以指向已读或已写数据后的下一字节,因此,连续的调用 read()和 write()函数将使得读写按顺序 递增,对文件进行操作。我们先来看看 lseek 函数的原型,如下所示(可通过"man 2 lseek"查看):
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
函数参数和返回值含义如下:
- fd:文件描述符。
- offset:偏移量,以字节为单位。
- whence:用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
- SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算);
- SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为 负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
- SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负, 如果是正数表示往后偏移、如果是负数则表示往前偏移。
返回值:成功将返回从文件头部开始算起的位置偏移量(字节为单位),也就是当前的读写位置;发生 错误将返回-1。
4.2使用示例:
(1)将读写位置移动到文件开头处:
ff_t off = lseek(fd, 0, SEEK_SET); if (-1 == off) return -1;
(2)将读写位置移动到文件末尾:
off_t off = lseek(fd, 0, SEEK_END); if (-1 == off) return -1;
(3)将读写位置移动到偏移文件开头 100 个字节处:
off_t off = lseek(fd, 100, SEEK_SET); if (-1 == off) return -1;
(4)获取当前读写位置偏移量:
off_t off = lseek(fd, 0, SEEK_CUR); if (-1 == off) return -1;
函数执行成功将返回文件当前读写位置。
五:案例练习
题目1
(1)打开一个已经存在的文件(例如 src_file),使用只读方式;然后打开一个新建文件(例如 dest_file), 使用只写方式,新建文件的权限设置如下: 文件所属者拥有读、写、执行权限; 同组用户与其他用户只有读权限。 从 src_file 文件偏移头部 500 个字节位置开始读取 1Kbyte 字节数据,然后将读取出来的数据写入到 dest_file 文件中,从文件开头处开始写入,1Kbyte 字节大小,操作完成之后使用 close 显式关闭所有文件, 然后退出程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void main()
{
char buff[ 1024];
int fd = open("./file",O_RDONLY);
if(-1 == fd)
printf("error\n");
int fd1 = open("./test_file",O_WRONLY|O_CREAT|O_TRUNC,S_IRWXU|S_IRGRP|S_IROTH);
//文件所属者拥有读、写、执行权限; 同组用户与其他用户只有读权限,
//O_TRUNC,文件中本来是有内容的,先清除再写入
if(-1 == fd1)
printf("error\n");
lseek(fd, 500, SEEK_SET);
int len = read(fd,buff,1024);
int len2 = write(fd1,buff,len);
printf("在file读取了 %d 字节 \n将%d字节写入test_file\n",len,len2);
close(fd);
close(fd1);
}
题目2
(2)通过 open 函数判断文件是否存在(例如 test_file),并将判断结果显示出来。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void main(int argc,char **argv)
{
int fd = open(argv[1],O_RDONLY);
printf("fd = %d\n",fd);
if(-1 == fd) //文件打开失败
{
printf("error , no:%s\n",argv[1]);//没找到需要的文件
}
else//文件打开成功
{
printf("find %s susser\n",argv[1]);//打印出存在的文件名
}
close(fd);
}
笔者在 /home/chen/桌面路径下的文件夹中有test_file,而没有test_fil,所以执行./a.out /home/chen/桌面/test_file 这条指令时,fd = 3,所以找到了需要的文件
题目3
(3)新建一个文件(例如 new_file),新建文件的权限设置为: 文件所属者拥有读、写、执行权限;同组用户与其他用户只有读权限。 使用只写方式打开文件,将文件前 1Kbyte 字节数据填充为 0x00,将下 1Kbyte 字节数据填充为 0xFF, 操作完成之后显式关闭文件,退出程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void main()
{
char buf[] = {"0x00"};
char *buf1 = "0xFF";
int fd = open("./new_file",O_WRONLY|O_CREAT,S_IRWXU| S_IRGRP| S_IRGRP);
if(-1 == fd)
puts("open new_file error\n");
int LEN = 0; //统计以写入字节数
/*写入1000字节 0x00部分*/
while(LEN<1000)
{
int len = write(fd,buf,4); //len为每次写入成功的字节数
LEN = LEN+len;
if( 0 == LEN%100){ //每写入100字节的0x00换一次行,方便看数据,换行符不计入题目的字符字节数
write(fd,"\n",strlen("\n"));
}
}
/*写入1000字节 0xFF部分*/
LEN = 0;
while(LEN<1000)
{
int len = write(fd,buf1,4);
LEN = LEN+len;
if( 0 == LEN%100){ //每写入100字节的0x00换一次行,方便看数据,换行符不计入题目的字符字节数
write(fd,"\n",strlen("\n"));
}
}
close(fd);
}
执行结果:
题目4
(4)打开一个已经存在的文件(例如 test_file),通过 lseek 函数计算该文件的大小,并打印出来。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void main(int argc,char **argv)
{
int fd = open(argv[1],O_RDONLY);
int len = lseek(fd,0,SEEK_END);
printf("文件%s 的大小为%d个字节\n",argv[1],len);
close(fd);
}
指令格式为./a.out 《需要计算文件大小的文件名加路径》