本文算是入门系统调用,以文件io为起点,研究系统调用的API,包括打开文件、关闭文件、从文件中读取数据和向文件中写数据,当然是对磁盘文件的I/O操作,本文只是作为起点,起点稍微低点有利于持续学习信心,坑以后埋。
文件描述符
如果想对一个文件进行读写操作,首先需要找到这个文件,那我们如何找到这个文件呢?这个文件描述符就可以找到所需要读写的文件。对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,使用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。
打开一个文件:open()
open()既能打开一个已经存在的文件,也能创建并打开一个新文件。
#include <fcntl.h>
// 打开一个已经存在的磁盘文件
int open(const char *pathname, int flags);
// 打开磁盘文件, 如果文件不存在, 就会自动创建
int open(const char *pathname, int flags, mode_t mode);
成功返回文件描述符,发生错误则返回-1
参数介绍
- pathname :被打开文件的文件名
- flags :用于指定用何种方式打开文件,具体参数值如下
必须要指定的属性 , 以下三个属性不能同时使用,只能任选其一
O_RDONLY: 以只读方式打开文件
O_WRONLY: 以只写方式打开文件
O_RDWR: 以读写方式打开文件
可选属性 , 和上边的属性一起使用
O_APPEND: 新数据追加到文件尾部,不会覆盖文件的原来内容
O_CREAT: 如果文件不存在,创建该文件,如果文件存在什么也不做
O_EXCL: 检测文件是否存在,必须要和 O_CREAT 一起使用,不能单独使用: O_CREAT | O_EXCL
检测到文件不存在,创建新文件
检测到文件已经存在,创建失败,函数直接返回 - 1(如果不添加这个属性,不会返回 - 1) - mode :在创建新文件的时候才需要指定这个参数的值,用于指定新文件的权限,是八进制数,如0700、0777
关闭一个文件:close()
通过 open 函数可以让内核给文件分配一个文件描述符,如果需要释放这个文件描述符就需要关闭文件。对应的这个系统函数叫做 close
#include <unistd.h>
int close(int fd);
- 函数参数: fd 是文件描述符,是 open () 函数的返回值
- 函数返回值:函数调用成功返回值 0, 调用失败返回 -1
创建一个新文件:creat()
使用creat()函数创建一个新文件,并以只写的方式打开;
#include <fcntl.h>
int creat(const char * pathname, mode_t mode)
pathname:要创建的文件名;
mode:要创建的新文件的权限;
调用成功返回1,失败返回-1;
等同于
//建议使用下面的代码创建新文件
open(pathname,O_CREATE | O_WRONLY | O_TRUNC , mode);
文件定位:lseek()
每一个打开的文件都有一个与其相关的文件偏移量,在系统默认的情况下,新打开的文件的偏移量为0,它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
- 参数:
fd: 文件描述符,open () 函数的返回值,通过这个参数定位打开的磁盘文件
offset: 偏移量,需要和第三个参数配合使用
whence: 通过这个参数指定函数实现什么样的功能
SEEK_SET: 从文件头部开始偏移 offset 个字节
SEEK_CUR: 从当前文件指针的位置向后偏移 offset 个字节,offset可以是负数
SEEK_END: 从文件尾部向后偏移 offset 个字节 - 返回值:
成功:新的偏移量
失败: -1
一些小应用
将偏移量移动到文件头部
lseek(fd,0,SEEK_SET);
获取当前文件偏移量
lseek(fd,0,SEEK_CUR);
获取文件的大小
lseek(fd,0,SEEK_END);
文件空洞
lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操
作。然后,该偏移量用于下一个读或写操作。
文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的
下一次写将加长该文件,并在文件中构成一个空洞,这一点是允许的。
位于文件中但没有写过的字节都被读为0。
文件中的空洞并不要求在磁盘上占用存储区。具体处理方式与文件系统的实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但是对于原文件尾端和新开始写位置之间的部分则不需要分配磁盘块。

读取文件内容:read()
read 函数用于读取文件内部数据,在通过 open 打开文件的时候需要指定读权限
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
- 参数:
fd: 文件描述符,open () 函数的返回值,通过这个参数定位打开的磁盘文件
buf: 是一个传出参数,指向一块有效的内存,用于存储从文件中读出的数据
传出参数:类似于返回值,将变量地址传递给函数,函数调用完毕,地址中就有数据了
count: buf 指针指向的内存的大小,指定可以存储的最大字节数 - 返回值:
大于 0: 从文件中读出的字节数,读文件成功
等于 0: 代表文件读完了,读文件成功
-1: 读文件失败了
数据写入文件:write()
write 函数用于将数据写入到文件内部,在通过 open 打开文件的时候需要指定写权限
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
- 参数:
fd: 文件描述符,open () 函数的返回值,通过这个参数定位打开的磁盘文件
buf: 指向一块有效的内存地址,里边有要写入到磁盘文件中的数据
count: 要往磁盘文件中写入的字节数,一般情况下就是 buf 字符串的长度,strlen (buf) - 返回值:
大于 0: 成功写入到磁盘文件中的字节数
-1: 写文件失败了
文件读写实现的文件拷贝(简单实现cp命令)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define MAX 1024
int main(int argc, char const *argv[]){
char buf[MAX];
int in,out;
int num;
if (argc != 3)
exit(1);
if ((in = open(argv[1],O_RDONLY)) == -1){
perror("fail to open");
exit(1);
}
if ((out = open(argv[2], O_WRONLY | O_TRUNC | O_CREAT)) == -1){
perror("fail to open");
exit(1);
}
while ((num = read(in,buf,MAX)) > 0){
if (write(out,buf,num)!=num){
perror("fail to write");
exit(1);
}
}
if (num < 0){
perror("fail to read");
exit(1);
}
close(in);
close(out);
return 0;
}
使用
$ gcc my_cp.c -o my_cp
$ ./my_cp myfile.txt myfile1.txt #myfile.txt是源文件,要被复制的文件 ;myfile1.txt 是目标文件,没有就会新建,有就覆盖
系统调用学习不仅仅是这些API的使用方法更多是一些Linux理论概念上的自我理解,深入学习.......
(待续)
参考
《UNIX环境高级编程》
《Linux/UNIX系统编程手册》
《Linux C程序设计 王者归来》
《Linux C 一站式学习》
网友评论