Linux 系统调用 API 之文件 I

Linux 系统调用 API 之文件 I/O

​开始之前

正是因为 Linux 系统一切皆文件,我们便将文件的输入/输出作为学习 Linux 系统调用 API 的第一部分。你将从理解文件描述符的概念开始,一直学到打开文件、关闭文件、从文件中读出、向文件写入数据等接口。

系统帮助手册(manual)上的第 2 章节,便为系统调用的相关手册页,里面有所需的头文件列表及其函数原型。

你可以执行以下命令获取相关函数的使用方法。里面有所需的头文件列表及其函数原型。

man 2 open 

如果你还对内核、系统调用、库函数等概念不清楚,那欢迎你在我的前文中寻找答案:

文件描述符(file descriptor)

fd 即为文件描述符约定俗成的标示,如果你刚刚查了 open() 函数的帮助手册,便会得到以下的说明:

The return value of open() is a file descriptor, a small, nonnegative integer that is used in subs quent system calls (read(2), write(2), lseek(2), fcntl(2), etc.) to refer to the open file.

open() 的返回值便是一个文件描述符,一个小的非负整数,用以指代打开为文件,同时也将用于 read(2), write(2), lseek(2), fcntl(2) 等系统调用。

open

open() 函数通过指定的参数 pathname 来打开文件,如果指定的文件不存在(同时 flags 参数中有指明 O_CREAT),那么 open 将创建之。

函数原型如下:

int open(const char *pathname, int flags, mode_t mode);

参数 flags 包含以下的访问权限,O_RDONLY, O_WRONLY, O_RDWR or O_CREAT 他们分别要求着文件以只读、只写、读写的方式打开或创建文件。

// 代码示例
#include<stdio.h>
#include
#include
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
nt main(int argc, char* argv[])
// 向 main 函数传递参数,argc为接收参数的总数,argv[0] 为此源码文件名,argv[1] 为接收的第一个参数,以此类推
{
if(argc < 2)
{
printf("Please input filename!\\n");
exit(1);
}

else
{
int fd;
umask(0000);
// 新建文件的权限不仅仅依赖于参数 mode,也受制于进程的 umask 值。执行命令 man umask 以深入了解

fd = open(argv[1], O_RDWR | O_CREAT, 0666);
if(fd < 0)
{ // open 打开失败返回 -1
printf("error\\n");
exit(1);
}
else
{
printf("success=%d\\n", fd);
close(fd);
printf("closed\\n");
}
return 0;
}
}
/<unistd.h>/<stdlib.h>/<fcntl.h>
/<stdio.h>

read

read() 函数试图从文件描述符 fd 中读取指定字节数数据至内存缓冲中。

函数原型如下:

ssize_t read(int fd, void *buf, size_t count);

参数 buf 为用来存放数据的内存缓冲地址,参数 count 为指定的读取字节数,同时 read 函数返回实际读取到的字节数,-1 表示错误。

系统调用不会分配内存缓冲区以返回调用者。所以,必须预先分配大小合适的缓冲区并将其指针传递给系统调用。与此相反,有些库函数却会分配内存缓冲区用以返回信息给调用者。

// 代码示例
#define MAX_READ 20
char buf [MAX_READ];
f ( read(argv[1], buf, MAX_READ) + 1 )
exit(1);

printf("The read data is : %s\\n", buf);

write

与 open() 的调用相似,write() 函数最多从内存缓冲 buf 中读取参数 count 所定义的字节单位数据量,并写入至 fd 所指向的文件中。若调用成功则返回实际写入文件的字节数,错误返回 -1。

函数原型:

ssize_t write(int fd, const void *buf, size_t count);

在理解时需要注意,read() 与 write() 成功读入或写入数据后,并不会将操作的内容输出至屏幕。

你要关注的是 buffer,通过字符串输出以查看缓冲区内容是否正确,访问文件内容以查看是否写入成功。

close

close() 关闭一个文件描述符,以不再指向任何文件。函数原型如下:

int close(int fd);

成功返回 0,失败返回 -1,因此我们同样可以对其进行错误捕获。

lseek

内核会记录每个打开文件的偏移量,文件偏移量是指执行下一个 read() 或 write() 操作时文件的起始位置。

文件打开时,会将文件偏移量设置为指向文件的开始,而每次的 read() 或 write() 操作便会自动对其进行调整,以指向已读或已写数据后的下一个字节。

以保证一次打开多次读写的完整性、不重复。对一次 write() 调用的编译后的两次执行,即为对目标文件开头处的重复写入。而对两次 write() 调用的编译后的一次执行,将有两次写入痕迹。

// 函数原型
off_t lseek(int fd, off_t offset, int whence);

lseek() 通过参数 offset 与 whence,对 fd 所指向的已打开的文件进行偏移量的调整。

参数 offset 为以字节为单位的偏移量,参数 whence 则为从何处进行偏移,whence 的参照点有:

  • SEEK_SET:从文件头部开始偏移 offset 个字节
  • SEEK_CUR:相对于当前文件偏移量,调整 offset 个字节
  • SEEK_END:从文件尾部开始偏移 offset 个字节,正数向后,负数向前
// 体验代码
#include<stdio.h>
#include<string.h>
#include
#include
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
it main(int argc, char* argv[])
{
if(argc < 2)
{
printf("Please input filename!\\n");
exit(1);
}
else
{
int fd;
umask(0000);
fd = open(argv[1], O_RDWR|O_CREAT, 0644);
if(fd < 0)
{
printf("error\\n");
exit(1);
}
else
{
printf("open success=%d\\n", fd);
// write

char buf[1024] = "hello world\\n";

if( write(fd, buf, strlen(buf)) + 1)
{
printf("buf=%s\\n", buf); // 若写入成功则将内存缓冲区内容输出至屏幕
// lseek
lseek(fd, 6, SEEK_SET); // 从文件头开始,向后偏移 6 个字节
char buf2[1024];

if(read(fd, buf2, 1024) + 1) // 调整了偏移量后,重新对 fd 进行读取,此时输出因为 world
printf("buf2=%s\\n", buf2);
}
close(fd);
printf("closed\\n");
}
return 0;
}
}
/<unistd.h>/<stdlib.h>/<fcntl.h>
/<string.h>/<stdio.h>

觉得我写的不错的话,不妨添加关注吧!


分享到:


相關文章: