C语言中常用的文件操作函数有fopen(),fread(),fwrite(),fclose(),fseek(),这些函数称为库函数。
Linux中常用的文件操作函数有open(),read(),write(),close(),seek(),stat(),这些函数称为系统调用函数。
系统调用函数:系统调用函数是操作系统为内核代码提供给用户或上层使用的一些函数接口,调用在用户态,执行在内核态。
库函数:库函数是把一些常用的函数编写完放到一个文件里,供不同的人进行调用,库函数在用户态调用,在用户态实现,但是有些库函数需要转调系统调用函数,若库函数需要底层支持,就需要调用系统调用函数,若库函数不需要底层支持,就不需要调用系统调用函数。
例如使用printf(),scanf(),这两个函数一个是从界面获取数据,一个是将数据打印到界面上,这两个操作都需要底层的支持,那么就需要经过操作系统,调用系统调用函数;例如使用strlen(),strcpy(),这两个操作不需要底层的支持,所以不会调用系统调用函数。
下面来看几个常用的文件操作函数:
(1)open函数
调用open函数可以打开或创建一个文件
#include <fcntl.h>
int open(const char *pathname,int flag,/*mode_t mode*/);
返回值:若成功返回文件描述符,若失败返回-1
pathname:要打开或创建文件的名字;
flag:打开的方式,有以下几种方式:
O_RDONLY:只读;
O_WRONLY:只写;
O_RDWR:读写;
O_APPEND:每次写时追加到文件的尾端;
O_CREAT:创建一个文件,只有在文件不存在时,才会生效,创建一个新文件;
O_EXCL:如果同时和O_CREAT使用,而文件已经存在,则会出错,用此可以测试一个文件是否存在,如果不存在,则创建此文件;
mode:创建问件时,指定文件的权限;
(2)read函数
调用read函数可以从打开的文件中读数据
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t size);
返回值:若成功返回读取到的字节数,若已到文件结尾则返回0,若失败返回-1
fd:读取的文件的文件描述符,由open函数的返回值指定;
buf:读取的数据存储的起始位置;
size:一次最多读取的字节个数,size <= 缓冲区的大小;
(3)write函数
调用write函数可以向打开的文件写数据
#include <unistd.h>
ssize_t write(int fd,void *buf,size_t size);
返回值:若成功返回已经写入的数据的字节数,若失败返回-1
fd:将数据写入的文件的文件描述符,由open函数的返回值指定;
buf:写入的数据的起始位置;
size:写入数据的字节数;
(4)close函数
调用close函数可以关闭一个打开的文件描述符
#include <unistd.h>
ssize_t close(int fd);
返回值:若成功返回0,如失败返回-1
(5)lseek函数
调用lseek函数可以显式地为一个打开的文件设置其偏移量
#include <unistd.h>
int lseek(int fd,int offset,int flag);
返回值:若成功返回新的文件偏移量,若失败返回-1
fd:操作的文件的的文件描述符;
offset:偏移量
flag:移动标记,有以下几种:
SEEK_SET:将该文件的偏移量设置为距文件开始处offset个字节;
SEEK_CUR:将该文件的偏移量设置为当前值加offset个字节;
SEEK_END:将该文件的偏移量设置为文件长度offset个字节;
(6)stat、fstat、lstat函数
这三个函数都是获取文件的属性信息
#include <sys/stat.h>
int stat(const char *restrict pathname,struct stat *restrict buf);
int fstat(int fd,struct stat *buff);
int lstat(const char *restrict pathname,struct stat *restrict buf);
三个函数的返回值:若成功返回0,若失败返回-1
stat函数返回与此命名文件pathname有关的信息结构,fstat函数获取已在文件描述符fd上打开的文件的有关信息,lstat函数类似与stat函数,但是当命名文件pathname是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用的有关信息;buff用于存放文件信息,buff指向stat结构体。
struct
{
mode_t st_mode; //文件类型和模式(权限)
ino_t st_ino; //索引节点号
dev_t st_dev; //设备号
sev_st st_rdev; //特殊文件的设备号
nlink_t st_nlink; //链接数
uid_t st_uid; //用户id
gid_t st_gid; // 组id
off_t st_size; //字节数
time_t st_atime; //最后访问时间
time_t st_mtime; //最后修改时间
time_t st_ctime; //文件最后的状态改变
blksize st_dlksize; //最佳I/O块大小
blkcnt_t st_blocks; //分配的磁盘块数
};
使用stat函数最多的可能是ls,-l命令,用其可以获得有关一个文件的所有信息。
练习:将“hello world”写到文件a.text,并将其读出
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd=open("./a.text",O_RDWR | O_CREAT,0664);
assert(fd != -1);
char buff[128]="hello world";
write(fd,buff,strlen(buff));
char readbuff[128]={0};
lseek(fd,0,SEEK_SET);
read(fd,readbuff,127);
printf("readbuff: %s\n",readbuff);
close(fd);
}
以open函数为例来看怎么从用户态切换到内核态
1、触发0x80中断; 2、保存程序上下文,即当前程序的运行状态; 3、将函数对应的系统调用号保存到eax寄存器中;
系统调用函数触发0x80中断,并且将系统调用号5存储在eax寄存器中,然后进入内核,内核开始执行中断处理程序,在系统调用表中查找系统调用号对应的系统内核函数sys_open()并且调用,执行完成后又将返回值通过eax寄存器传递回用户空间。