1、系统调用的本质
所有操作的本质都是要下发命名到硬件,在Linux操作系统中提供了linux内核来驱动操作硬件、为了方便用户操作相关硬件功能,所以提供了一些函数接口,用于操作Linux内核,进而由内核来操作硬件。这些接口称之为系统调用。
在Linux内核中,大多数的代码也是系统调用。在应用层调的一些函数接口,有些也是在函数接口内在调用内核中的系统调用函数。
文件io:称之为系统调用
标准io:称之为库函数
2、库函数
库函数的本质是系统调用,系统调用函数只要执行一次,就需要对Linux内核访问一次,这样虽然效率较高,但是占用的资源比较大。
库函数提供了一种方式,如果需要执行多次系统调用,在执行之前,会在内存中开辟一块空间,称之为缓冲区,缓冲区的目的就是将多次执行的系统调用的数据先保存起来,然后只需要执行一次系统调用即可,这样节省空间开销。库函数的目的就是为了减少系统调用的次数。
不论是系统调用还是库函数,最终目的都是操作物理内存。
3、系统调用函数
文件io是系统调用,没有缓冲区,例如:open、read、write、lseek等。
标准io是库函数,有缓冲区,例如:fopen、fread、fwrite、ftell、fseek、fgets、fputs等。
标准io通过文件指针(FILE *)来操作文件
文件io通过文件描述符(int)对文件进行操作
当程序运行时或者创建了一个新的进程时,系统都会自动分配三个文件描述符
标准io 文件io
标准输入 stdin 0
标准输出 stdout 1
标准出错输出 stderr 2
下面对一些常用的系统调用函数简单介绍。
1、open()
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:打开或者创建一个文件,返回一个文件描述符
参数:
pathname:文件名,可以添加路径,默认为当前路径
flags:访问权限标志位
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 可读可写
O_CREAT 如果文件不存在则创建,如果会用此标志位,必须有第三个参数
O_TRUNC 如果文件存在则清空
O_APPEND 如果文件存在则追加
O_EXCL 常与O_CREAT一起使用,表示如果文件存在则报错
mode:文件创建的权限,一般传八进制数即可,例如0664
返回值:
成功:文件描述符
失败:-1
2、close()
#include <unistd.h>
int close(int fd);
功能:关闭一个文件描述符,如果关闭文件描述符,
则无法再通过这个文件描述符对文件进行操作
参数:
fd:指定的文件描述符
返回值:
成功:0
失败:-1
3、perror()
#include <stdio.h>
void perror(const char *s);
功能:当函数调用失败后可以输出错误信息
参数:
s:错误信息的提示信息
返回值:
无
4、errno
errno变量在errno.h头文件中定义,
是一个全局变量,可以在任意位置使用,用于保存函数调用后的错误码
例如:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
//使用open函数创建或者打开一个文件
//if((fd = open("file.txt", O_RDONLY)) == -1)
//if((fd = open("file.txt", O_RDONLY | O_CREAT | O_TRUNC, 0664)) == -1)
//if((fd = open("file.txt", O_RDONLY | O_CREAT | O_APPEND, 0664)) == -1)
if((fd = open("file.txt", O_RDONLY | O_CREAT | O_EXCL, 0664)) == -1)
{
//errno:用于函数执行失败后保存其失败的错误码,需要头文件,是一个全局变量
//printf("errno = %d\n", errno);
//perror:当函数调用失败时,会输出错误信息
perror("fail to open");
return -1;
}
printf("fd = %d\n", fd);
//关闭文件描述符
close(fd);
return 0;
}
5、read()
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据
参数:
fd:指定的文件描述符
buf:保存读取到的数据
count:最大一次读取多少个字节
返回值:
成功:实际读取的字节数
失败:-1
注意:如果读取到文件末尾,返回0
例如:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define N 6
int main(int argc, char const *argv[])
{
//从终端读取数据
//从终端读取数据时,会把\n也读取到
//如果输入的数据大于设置读取的最大个数,会读取设置的字节数
#if 0
char buf[N] = "";
if(read(0, buf, sizeof(buf)) == -1)
{
perror("fail to read");
return -1;
}
printf("buf = %s", buf);
#endif
//从文件中读取数据
int fd;
if((fd = open("file.txt", O_RDONLY)) == -1)
{
perror("fail to open");
return -1;
}
//注意:使用read函数读取文件内容是,要以返回值为标准表示实际读取的个数
ssize_t bytes;
char buf[40] = "";
if((bytes = read(fd, buf, sizeof(buf))) == -1)
{
perror("fail to read");
return -1;
}
printf("buf = %s\n", buf);
printf("bytes = %ld\n", bytes);
char buf1[40] = "";
if((bytes = read(fd, buf1, sizeof(buf1))) == -1)
{
perror("fail to read");
return -1;
}
printf("buf1 = %s\n", buf1);
printf("bytes = %ld\n", bytes);
return 0;
}
6、write()
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:向文件写入数据
参数:
fd:指定的文件描述符
buf:要写入的数据
count:要写入的数据的长度
返回值:
成功:实际写入的字节数
失败:-1
例如:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//向终端写入数据
#if 0
if(write(1, "hello world\n", 13) == -1)
{
perror("fail to write");
return -1;
}
#endif
//向文件中写入数据
int fd;
if((fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1)
{
perror("fail to open");
return -1;
}
if(write(fd, "hello world", 12) == -1)
{
perror("fail to write");
return -1;
}
close(fd);
return 0;
}
7、lseek()
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:设置或者获取文件的偏移量
参数:
fd:指定的文件描述符
offset:偏移的位置,可正可负
whence:相对位置
SEEK_SET 文件起始位置
SEEK_CUR 文件当前位置
SEEK_END 文件末尾位置(最后一个字符的下一个位置)
返回值:
成功:返回文件的当前偏移量
失败:-1
例如
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int fd;
//if((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0664)) == -1)
//注意:如果文件io中的open函数使用O_APPEND或者标准io的fopen函数使用a或者a+
// 这样做无法修改写操作的偏移量,只能修改读操作的偏移量
// 写操作只能在末尾处去写,而读操作可以指定偏移量
if((fd = open(argv[1], O_RDWR | O_CREAT | O_APPEND, 0664)) == -1)
{
perror("fail to open");
return -1;
}
char buf[128] = "hello world, nihao beijing, nihao chengdu\n";
if(write(fd, buf, strlen(buf)) == -1)
{
perror("fail to write");
return -1;
}
//不管是read还是write,执行完毕后都会修改文件的偏移量
//获取当前文件的偏移量
printf("offset = %ld\n", lseek(fd, 0, SEEK_CUR));
//修改文件的偏移量
lseek(fd, 0, SEEK_SET);
printf("offset = %ld\n", lseek(fd, 0, SEEK_CUR));
char text[128] = "";
if(read(fd, text, sizeof(text)) == -1)
{
perror("fail to read");
return -1;
}
printf("text = %s\n", text);
lseek(fd, 10, SEEK_SET);
write(fd, "666666", 6);
return 0;
}
8、remove()
#include <stdio.h>
int remove(const char *pathname);
功能:删除文件或者目录
参数:
pathname:文件名或者目录名
返回值:
成功:0
失败:-1
4、库函数
1、fopen()
#include <stdio.h>
FILE * fopen(const char * path,const char * mode);
参数path字符串包含欲打开的文件路径及文件名,参数mode字符串则代表着流形态。
返回值:文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回NULL,并把错误代码存在errno 中
r 为 读操作 打开 文本文件. 流 被定位于 文件 的 开始.
r+ 为读写操作打开文本文件. 流 被定位于 文件 的 开始.
w 为写操作创建文本文件, 或者 将 已经 存在的 文件长度 截断为 零. 流 被定位于 文件 的 开始.
w+ 为读写操作打开文件. 如果 文件 不存在, 就 创建 它, 否则 将它 截断. 流 被定位于 文件 的 开始.
a 为追加操作 (在文件尾写) 打开 文件. 如果 文件 不存在, 就 创建 它. 流 被定位于 文件 的 末尾.
a+ 为追加操作 (在文件尾写) 打开 文件. 如果 文件 不存在, 就 创建 它. 读文件的初始位置 是 文件 的 开始, 但是 写文件 总是 被追加到 文件 的 末尾.
与open对比
2、fread()
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件中读取数据
参数:
ptr:保存读取的数据
size:每次读取的字节数
nmemb:一共读取的次数
stream:文件指针
返回值:
成功:实际读取的次数(对象数、块数)
失败:0
如果文件内容读取完毕,返回0
例如:将文件中信息读到结构体中
#include <stdio.h>
typedef struct{
int a;
int b;
char c;
char d[32];
}MSG;
int main(int argc, char const *argv[])
{
//使用fread从文件中读取数据
FILE *fp;
if((fp = fopen("file.txt", "r")) == NULL)
{
perror("fail to fopen");
return -1;
}
// char buf[32] = {0};
// fread(buf, 2, 5, fp);
// printf("buf = %s\n", buf);
// int num;
// fread(&num, 4, 1, fp);
// printf("num = %d\n", num);
// int b[4] = {0};
// fread(b, 4, 4, fp);
// printf("%d %d %d %d\n", b[0], b[1], b[2], b[3]);
MSG msg;
fread(&msg, sizeof(msg), 1, fp);
printf("%d - %d - %c - %s\n", msg.a, msg.b, msg.c, msg.d);
return 0;
}
3、fwrite()
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:向文件中写入数据
参数:
ptr:要写入的数据
size:一次写入的字节数
nmemb:一共写入的次数
stream:文件指针
返回值:
成功:实际写入的次数
失败:0
例如:
#include <stdio.h>
typedef struct{
int a;
int b;
char c;
char d[32];
}MSG;
int main(int argc, char const *argv[])
{
//使用fwrite向文件写入数据
FILE *fp;
if((fp = fopen("file.txt", "w")) == NULL)
{
perror("fail to fopen");
return -1;
}
//写入字符串
//char buf[] = "1234567890";
//fwrite(buf, 2, 5, fp);
//写入整数
//int a = 97868;
//fwrite(&a, 4, 1, fp);
//写入数组
//int a[4] = {100, 200, 300, 400};
//fwrite(a, 4, 4, fp);
//写入结构体
MSG msg = {666, 888, 'w', "zhangsan"};
fwrite(&msg, sizeof(msg), 1, fp);
return 0;
}
4、fgets()、fputs()
fgets()
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:从文件中读取内容
参数:
s:保存读取到的内容
size:每次读取的最大个数
stream:文件指针
返回值:
成功:读取的数据的首地址
失败:NULL
如果文件内容读取完毕,也返回NULL
fputs()
#include <stdio.h>
int fputs(const char *s, FILE *stream);
功能:向文件写入数据
参数:
s:要写入的内容
stream:文件指针
返回值:
成功:写入文件内容的字节数
失败:EOF
5、fseek()
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
功能:设置文件位置指针的偏移量
参数:
stream:文件指针
offset:偏移量
可正可负也可为0
whence:相对位置
SEEK_SET 文件起始位置
SEEK_CUR 文件当前位置
SEEK_END 文件末尾位置(最后一个字符后面一个位置)
返回值:
成功:0
失败:-1
6、rewind()
#include <stdio.h>
void rewind(FILE *stream);
功能:将文件位置定位到起始位置
参数:
stream:文件指针
返回值:无
rewind(fp) <==> fseek(fp, 0, SEEK_SET);
7、ftell()
#include <stdio.h>
long ftell(FILE *stream);
功能:获取当前文件的偏移量
参数:
stream:文件指针
返回值:
获取当前文件的偏移量
例如:
#include <stdio.h>
int main(int argc, char const *argv[])
{
//写操作的文件偏移量
#if 0
FILE *fp;
if((fp = fopen("file.txt", "w")) == NULL)
{
perror("fail to fopen");
return -1;
}
fputs("abcdefghijklmnopq", fp);
//使用ftell获取文件偏移量
printf("offset = %ld\n", ftell(fp));
//使用fseek修改文件偏移量
//rewind(fp);
//fseek(fp, 0, SEEK_SET);
fseek(fp, -5, SEEK_END);
fputs("12345", fp);
#endif
#if 0
FILE *fp;
if((fp = fopen("file.txt", "r")) == NULL)
{
perror("fail to fopen");
return -1;
}
char buf[32] = {0};
fgets(buf, 6, fp);
printf("buf = %s\n", buf);
printf("offset = %ld\n", ftell(fp));
fseek(fp, 3, SEEK_SET);
printf("offset = %ld\n", ftell(fp));
fgets(buf, 6, fp);
printf("buf = %s\n", buf);
#endif
FILE *fp;
//如果fopen打开或者创建一个文件时使用的是的a或者a+权限
//则写操作的偏移量无法改变,只能在文件的末尾位置
//但是读操作没有任何影响
if((fp = fopen("file.txt", "a+")) == NULL)
{
perror("fail to fopen");
return -1;
}
// char buf[32] = {0};
// fgets(buf, 6, fp);
// printf("buf = %s\n", buf);
// printf("offset = %ld\n", ftell(fp));
// fseek(fp, 3, SEEK_SET);
// printf("offset = %ld\n", ftell(fp));
// fgets(buf, 6, fp);
// printf("buf = %s\n", buf);
fputs("888888888", fp);
printf("offset = %ld\n", ftell(fp));
fseek(fp, 3, SEEK_SET);
printf("offset = %ld\n", ftell(fp));
fputs("666666", fp);
return 0;
}