每个系统都有自己的专属函数,我们习惯称其为系统函数。系统函数并不是内核函数,因为内核函数是不允许用户使用的,系统函数就充当了二者之间的桥梁,这样用户就可以间接的完成某些内核操作了。
在前面介绍了文件描述符,在Linux系统中必须要使用系统提供的IO函数才能基于这些文件描述符完成对相关文件的读写操作。这些Linux系统IO函数和标准C库的IO函数使用方法类似,函数名称也类似,下边开始一一介绍。
在Linux系统编程中,open
函数用于打开一个文件,并返回一个文件描述符,该文件描述符在后续的文件操作中被用作文件的标识符。如果文件不存在, 就创建一个新文件, open
函数的原型如下:
#include
#include
#include
/*
open是一个系统函数, 只能在linux系统中使用, windows不支持
fopen 是标准c库函数, 一般都可以跨平台使用, 可以这样理解:
- 在linux中 fopen底层封装了Linux的系统API open
- 在window中, fopen底层封装的是 window 的 api
*/
// 打开一个已经存在的磁盘文件
int open(const char *pathname, int flags);
// 打开磁盘文件, 如果文件不存在, 就会自动创建
int open(const char *pathname, int flags, mode_t mode);
参数介绍:
path
是要打开的文件的路径名。flags
是打开文件的标志,可以是以下一种或多种标志的组合:
O_RDONLY
:只读方式打开文件。O_WRONLY
:只写方式打开文件。O_RDWR
:读写方式打开文件。O_CREAT
:如果文件不存在,则创建文件。O_EXCL
:与 O_CREAT
一同使用,确保文件不存在(文件存在的话创建失败),避免覆盖已存在的文件。O_TRUNC
:如果文件存在,并且以写入方式打开,将文件截断为空。O_APPEND
:以追加方式打开文件,即写入数据时将数据添加到文件末尾。mode
是一个八进制数,指定文件的访问权限(仅在创建新文件时有效)。通常使用 S_IRUSR
、S_IWUSR
、S_IXUSR
等宏定义来设置文件权限。open
函数返回一个非负整数作为文件描述符,如果出错则返回 -1。可以**使用该文件描述符进行后续的文件读写操作。**以下是一个简单的例子:
#include
#include
#include
#include
int main() {
int fd;
// 打开文件 example.txt,如果不存在则创建,以读写方式打开
fd = open("example.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 使用文件描述符进行文件操作...
// 关闭文件
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
return 0;
}
请注意,错误处理是良好程序设计的一部分,上述示例中使用 perror
输出错误信息。
通过open函数可以让内核给文件分配一个文件描述符, 如果需要释放这个文件描述符就需要关闭文件。对应的这个系统函数叫做 close,函数原型如下:
#include
int close(int fd);
文件open.c
// open.c
#include
#include
#include
#include
#include
int main()
{
// 打开文件
int fd = open("abc.txt", O_RDWR); // 只读打开
// int fd = open("./new.txt", O_CREAT|O_RDWR, 0664); // 创建新文件
// int fd = open("./new.txt", O_CREAT|O_EXCL|O_RDWR); // 创建新文件之前, 先检测是否存在
// 文件存在创建失败, 返回-1, 文件不存在创建成功, 返回分配的文件描述符
if(fd == -1)
{
printf("打开文件失败\n");
}
else
{
printf("fd: %d\n", fd);
}
close(fd);
return 0;
}
read
函数用于从已打开的文件描述符中读取数据。其原型如下:
#include
ssize_t read(int fd, void *buf, size_t count);
其中:
fd
是已打开文件的文件描述符。buf
是用于存放读取数据的缓冲区的指针。count
是要读取的字节数。read
函数的返回值为实际读取的字节数,如果返回值为0,表示已到达文件末尾。如果返回值为-1,表示发生错误。
write 函数用于将数据写入到文件内部,在通过 open 打开文件的时候需要指定写权限,函数原型如下:
#include
ssize_t write(int fd, const void *buf, size_t count);
其中:
fd
是已打开文件的文件描述符。buf
是包含要写入数据的缓冲区的指针。count
是要写入的字节数。write
函数的返回值为实际写入的字节数。如果返回值为-1,表示发生错误。
#include
#include
#include
#include
#include
int main() {
// 1. 打开存在的文件a.txt, 读这个文件
int fd1 = open("./a.txt", O_RDONLY);
if (fd1 == -1) {
perror("open-readfile");
return -1;
}
// 2. 打开不存在的文件, 将其创建出来, 将从a.txt读出的内容写入这个文件中
int fd2 = open("copy.txt", O_WRONLY | O_CREAT, 0664);
if (fd2 == -1) {
perror("open-writefile");
return -1;
}
// 3. 循环读文件, 循环写文件
char buf[4096];
int len = -1;
while ((len = read(fd1, buf, sizeof(buf))) > 0) {
// 将读到的数据写到另一个文件中
write(fd2, buf, len);
}
// 4.关闭文件
close(fd1);
close(fd2);
return 0;
}
lseek
函数用于在文件中定位文件偏移量(file offset)。它的原型如下:
#include
#include
off_t lseek(int fd, off_t offset, int whence);
参数:
返回值:
主要可以进行这三种常用操作:
lseek(fd, 0, SEEK_SET);
lseek(fd, 0, SEEK_CUR);
lseek(fd, 0, SEEK_END);
当我们需要下载很大的文件时, 磁盘紧张, 如果不能马上将文件下载到本地, 磁盘可能会被占用, 这时可以现将字符写入到目标文件中, 让扩展的文件和被下载的文件一样大即可
使用 lseek 函数进行文件拓展必须要满足一下条件:
文件扩展举例:
// lseek.c
// 拓展文件大小
#include
#include
#include
int main()
{
int fd = open("a.txt", O_RDWR);
if(fd == -1)
{
perror("open");
return -1;
}
// 文件拓展, 一共增加了 1001 个字节
lseek(fd, 1024, SEEK_END);
write(fd, " ", 1);
close(fd);
return 0;
}
文件扩展前后文件的大小:
truncate/ftruncate 这两个函数的功能是一样的,可以对文件进行拓展也可以截断文件。使用这两个函数拓展文件比使用lseek要简单。这两个函数的函数原型如下:
// 拓展文件或截断文件
#include
#include
int truncate(const char *path, off_t length);
-
int ftruncate(int fd, off_t length);
参数:
返回值: 成功返回0; 失败返回值-1
truncate() 和 ftruncate() 两个函数的区别在于一个使用文件名一个使用文件描述符操作文件, 功能相同。
不管是使用这两个函数还是使用 lseek() 函数拓展文件,文件尾部填充的字符都是 0。
一般当程序出错时, 会有一个错误号, 每个错误号对应着错误信息
得到错误号,去查询对应的头文件是非常不方便的,我们可以通过 perror 函数将错误号对应的描述信息打印出来
#include
// 参数, 自己指定这个字符串的值就可以, 指定什么就会原样输出, 除此之外还会输出错误号对应的描述信息
void perror(const char *s);
例子: 在前面几个代码中都用到了perror
出错时的提示是这样的