标准IO
标准IO:标准IO是指 ANSI C中定义的用于I/O操作的一系列函数。只要系统中安装了C库,标准IO就可以调用。也就是说标准IO是建立在系统调用基础之上的,对用户提供统一的编程接口,本质上就是在不同系统上调用各自系统的系统调用,而对用户的接口不变,这样使其有了更好的移植性。除此之外,使用标准IO可以减少系统调用的次数,提高系统效率。
标准IO函数在执行时也会用到系统调用,在执行系统调用时,Linux系统必须从用户态切换到内核态,处理相应的请求,然后再返回到用户态。如果频繁的执行系统调用会增加系统的开销,为了避免这种情况,标准IO使用时在用户空间创建缓冲区,在合适的时机在通过系统调用访问实际的文件,从而减少了使用系统调用的次数。
标准IO操作对象为-普通文件
文件:保存在磁盘上的一组相关数据的有序集合。有序集合的名字叫文件名。
Linux系统上的文件分为七种:
- 普通文件 -
- 管道文件 p
- 链接文件 l
- 目录文件 d
- 套接字文件 s
- 块设备文件 b
- 字符设备文件 c
流:当使用标准IO打开一个文件时,就会创建一个FILE结构体描述该文件,我们把这个FILE结构体形象的称为流,标准IO函数都是基于流进行各种操作。
文件指针:指向一个打开文件的指针(硬盘中的文件被拷贝到内存中之后,会以FILE结构体的形态存在,要操作该文件必须使用文件指针)
标准IO中流的缓冲类型 - 行缓冲:\n (1024 byte)
- 全缓冲:缓存区填满内容才会溢出 (4096 byte)
- 不缓存:stderr
标准IO函数
函数名 | 函数功能 | 函数参数 | 函数返回值 |
---|---|---|---|
fopen | 打开文件 | const char* path(要打开的文件路径及文件名), const char *mode(打开方式) | 失败返回NULL,成功返回FILE* |
fclose | 关闭文件 | FILE* stream(已打开的流指针) | 失败返回EOF,成功返回0 |
fread | 读文件 | void* ptr(保存读到的数据),size_t size(读取的元素大小),size_t nmemb(读取元素的个数),FILE* stream(要读取的文件流) | 失败返回EOF,成功返回实际读取的个数 |
fwrite | 写文件 | void* ptr(保存要写入的数据),size_t size(写入的元素大小),size_t nmemb(写入元素的个数),FILE* stream(要写入的文件流) | 失败返回EOF,成功返回实际写入的个数 |
feof | 检测是否到达文件结尾 | FILE *stream(要检测的流指针) | 到达文件结尾返回非零,否则返回0 |
fseek | 设置光标位置 | FILE *stream(要定位的文件流),long offset(相对于基准的偏移量),int whence(基准值) | 失败返回EOF,成功返回0 |
ftell | 获取文件当前读写位 | FILE *stream(要定位的文件流) | 失败返回EOF,成功返回当前读写位置 |
文件IO
文件IO遵循POSIX相关标准,任何兼容POSIX标准的操作系统上都支持文件IO。
特点:
- 不带缓存
- 通过文件描述符来访问文件
在Linux系统中一切皆文件,Linux操作系统是基于文件概念的。Linux的文件系统由两层结构构建:第一层是虚拟文件系统(VFS),第二层是各种不同的具体的文件系统。VFS就是把各种具体的文件系统的公共部分抽取出来,形成一个抽象层,是系统内核的一部分。它位于用户程序和集体的文件系统之间,它对用户程序提供了标准的文件系统调用接口,可以接受用户层的系统调用。此外还支持多种具体文件系统之间的相互访问,接受内核其他子系统的操作请求。
通过以下命令可以查看系统中支持的文件系统:cat /proc/filesystems
文件描述符:文件描述符是一个非负整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。
通常一个进程启动时就会打开三个流:
- stdout: 标准输出流(默认情况下stdout是行缓冲,它的输出会放在一个buffer里面,只用到换行的时候才会输出到屏幕)
- stdin: 标准输入流
- stderr: 标准错误(无缓冲)
这三个流分别对应文件描述符0、1、2(对应的宏依次为:STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO)
文件IO相关函数
函数名 | 函数功能 | 函数参数 | 函数返回值 |
---|---|---|---|
open/creat | 打开或创建一个文件 | const char* pathname(被打开的文件名),int flags(文件打开的方式),int perms(新建文件的存取权限) | 调用成功返回文件描述符,失败返回-1,并设置errno |
close | 关闭文件 | int fildes(文件描述符) | 调用成功返回0,出错返回-1,并设置errno |
read | 读文件 | int fd(文件描述符),void* buf(读出数据的缓存区),size_t count(读出的字节数) | 出错返回-1,成功返回读取的字节数,返回0表示到达文件末尾 |
write | 写文件 | int fd(文件描述符),void* buf(读出数据的缓存区),size_t count(读出的字节数) | 出错返回-1,成功返回实际写入字节数 |
lseek | 设置光标位置 | int fd(文件描述符),off_t offset(相对于基准的偏移量),int whence(基准值) | 失败返回-1,成功返回当前读写位置 |
文件IO与标准IO的区别
- 文件IO又被称为低级磁盘IO,遵循POSIX相关标准,任何兼容POSIX标准的操作系统上都支持文件IO。标准IO被称为高级磁盘IO,遵循ANSI C标准,只要开发环境中有标准C库,就可以使用标准IO。
- 文件IO读写文件时,每次操作都会执行相关的系统调用,这样可以直接读取实际文件,但是频繁的系统调用会增加系统的开销。标准IO可以看做是在文件IO的基础上封装了缓冲机制,从而减少了系统调用的次数。
- 文件IO中用文件描述符表示一个打开的文件,可以访问不同类型的文件。而标准IO使用流表示一个打开的文件,只能访问普通文件。
文件锁
文件IO中的五个基本函数实现了文件的打开、读写操作。但是在文件被共享的情况下(及多个程序共同操作一个文件的情况)这个文件应该怎么进行操作而不会使文件混乱,Linux中通常采用的方法是给文件上锁。
文件锁包括建议性锁和强制性锁:
建议性锁 :要求每一个相关的程序在访问文件之前检查是否有锁存在,一般不建议使用建议性锁,因为无法保证每个程序都自动检查是否有锁。
强制性锁:是由内核执行的锁,当文件被锁上时,内核将阻止其它任何程序对该文件进行读写操作。
在Linux中,实现文件上锁的函数有 lockf() 和 fcntl()。
lockf():用于对文件施加建议性锁。
fcntl():不仅可以施加建议性锁还可以施加强制性锁,同时还可以对文件的某一记录上锁,也就是文件锁。具体的功能可以查看fcntl手册。
fcntl()函数:
//所需头文件
#include <sys/type,h>
#include <unistd.h>
#include <fcntl.h>
/*
函数原型:int fcntl(int fd, int cmd, ...);
fd: 文件描述符
cmd: F_GETLK: 检测文件锁状态
F_SETLK:设置lock描述符的文件锁
F_GETLKW:F_GETLK的阻塞版本
...: 如果cmd和锁的操作有关,第三个参数类型为 struct *flock
struc flock{
short l_type;F_WRLCK(读取锁、共享锁)、F_RDLCK(写入锁、排斥锁)、F_UNLCK(解锁)
off_t l_start; 加锁区域在文件中的相对位移量(字节)
short l_whence; 相对位移量的起点,同lseek的whence
off_t l_len; 加锁区域长度
pid_t l_pid; 具有阻塞当前进程的锁,其持有进程的进程号放在这里,仅由F_GETLK返回。
...
}
在无法获取锁时,会进入睡眠状态,如果可以获取锁或捕捉到信号则会返回。
*/
int FileLock(int fd, int type)
{
//给flock结构体赋值,要加锁整个文件,可以将l_start设置为0,l_whence设置为SEEK_SET,l_len设置为0。
struct flock lock;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = type;
lock.l_pid = -1;
//判断文件是否可以上锁
fcntl(fd, F_GETLK, &lock);
if (lock.l_type != F_UNLCK)
{
//判断不能上锁的原因
if (lock.l_type == F_RDLCK)
{
printf("read lock already set by %d\n", lock.l_pid);
}
else if (lock.l_type == F_WRLCK)
{
printf("write lock already set by %d\n", lock.l_pid);
}
}
lock.l_type = type;
//根据不同的type值进行阻塞式(F_SETLKW)上锁或解锁
if ( fcntl(fd, F_SETLKW, &lock)<0 )
{
printf("lock failed:%d", lock.l_type);
}
switch(lock.l_type)
{
case F_RDLCK:
printf("read lock set by %d\n", gitpid());
break;
case F_WDLCK:
printf("write lock set by %d\n", gitpid());
break;
case F_UNLCK:
printf("release lock set by %d\n", gitpid());
break;
}
return 0;
}