【Linux 应用编程】文件IO操作 - 常用函数
Linux 系统中的各种输入输出,设计为“一切皆文件”。各种各样的IO统一用文件形式访问。
文件类型及基本操作
Linux 系统的大部分系统资源都以文件形式提供给用户读写。这些文件可以分为:
- 普通文件:即一般意义上的磁盘文件;
- 设备文件:系统中的具体设备;
- 管道文件、FIFO 文件:用于进程间通信;
- 套接字(socket)文件:用于网络通信方面。
文件的通用操作为:打开、关闭、读、写、创建。对应 Linux 系统的 API 接口函数分别为 open()
、close()
、read()
、write()
、create()
。这些函数通过文件描述符 File Descriptor 实现 IO 操作。
文件描述符和文件描述符表
在进程中,通过 open 函数打开文件或通过 create 函数创建文件后,会返回一个整数,这个整数就是代表这个文件的文件描述符。通过 ulimit -n
命令可以查看每个进程最大支持同时打开多少文件。
操作系统在进程控制块(PCB,Process Control Block)中,帮每个进程维护了一个文件描述符表,进程打开的所有文件,都会在这个表里登记。打开文件得到的整型返回值,实际上就是指向表里某条记录的索引。后续执行读写操作时,通过传入的文件描述符,在文件描述符表进行查找,从而定位到文件的具体位置。
3个特殊的文件描述符
Linux 系统在启动时,标准 IO 会占用掉前 3 个文件描述符的位置:
- 标准输入 stdin 的文件描述符是 0
- 标准输出 stdout 的文件描述符是 1
- 标准错误stderr 的文件描述符是 2。
文件 I/O 常用头文件
部分函数需要同时引入多个头文件,是因为这些函数中用到的常量定义,跟函数定义不在同一个头文件里。
#include <sys/types.h> /* 定义数据类型,如 ssize_t,off_t 等 */
#include <fcntl.h> /* 定义 open,creat 等函数原型,创建文件权限的符号常量 S_IRUSR 等 */
#include <unistd.h> /* 定义 read,write,close,lseek 等函数原型 */
#include <errno.h> /* 与全局变量 errno 相关的定义 */
#include <sys/ioctl.h> /* 定义 ioctl 函数原型 */
open 和 creat 函数
函数头文件:
#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);
// 如果 flags 包含 O_CREAT,则相对于 creat 函数,必须指定 mode 参数
int creat(const char *pathname, mode_t mode);
参数:
- pathname:文件名称
- flags:标志,包含三种:
- 访问方式标志:必须包含。只读、只写或读写三种中的一个:
- O_RDONLY:只读
- O_WRONLY:只写
- O_RDWR:读写
- 文件创建标志:可以包含。
- O_CLOEXEC
- O_CREAT:如果文件不存在,则创建文件。文件的 UID 会被设置为进程的 UID,GID 会被设置为进程或父进程的 GID。此时必须指定第三个参数 mode。
- O_DIRECTORY
- O_EXCL:跟 O_CREAT 一同使用,确保一定会创建文件。如果文件已经存在则返回 -1。
- O_NOCTTY
- O_NOFOLLOW
- O_TRUNC:文件截断。
- O_TTY_INIT
- 文件状态标志:可以包含。该标志可以通过 fcntl 函数重新检索 retrieve 和修改。
- O_APPEND:追加模式。每次 write 或 lseek 函数执行前,指针会偏移到文件末尾。NFS 文件系统可能会有异常。
- O_ASYNC
- O_DIRECT
- O_LARGEFILE
- O_NOATIME
- O_NONBLOCK or O_NDELAY:非阻塞。
- O_PATH
- O_SYNC
- 访问方式标志:必须包含。只读、只写或读写三种中的一个:
- mode:创建文件时,设置文件权限。可以直接使用 0777 之类的数字,也可以用下面的常量:
- S_IRWXU 00700 user (file owner) has read, write and execute permission
- S_IRUSR 00400 user has read permission
- S_IWUSR 00200 user has write permission
- S_IXUSR 00100 user has execute permission
- S_IRWXG 00070 group has read, write and execute permission
- S_IRGRP 00040 group has read permission
- S_IWGRP 00020 group has write permission
- S_IXGRP 00010 group has execute permission
- S_IRWXO 00007 others have read, write and execute permission
- S_IROTH 00004 others have read permission
- S_IWOTH 00002 others have write permission
- S_IXOTH 00001 others have execute permission
返回值:报错时返回 -1,否则返回文件描述符。
示例:
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd;
char name[] = "666.txt";
// 如果文件不存在,就创建文件,权限为所有者RWX,组和其他人无权限
fd = open(name, O_RDONLY | O_CREAT, S_IRWXU);
// fd 小于 0 表示出错,需要处理
if (fd < 0) //...
printf("%d\n", fd);
close(fd);
// 如果文件已经存在,会把文件内容清空。权限为用户RW,组用户W,其他人R,即 0x321
fd = cerat(name, S_IRUSR | S_IWUSR | S_IWGRP | S_IROTH);
return 0;
}
close 函数
Linux 系统中,文件可以多次打开,例如多个进程同时打开一个文件,一个进程反复多次打开。内核记录了文件的打开次数,只要还有进程没关闭文件,就不会关闭文件。
close(fd);
read 函数
头文件:
#include <unistd.h>
函数原型:
ssize_t read(int fd, void *buf, size_t count);
read 函数会尝试从文件描述符 fd 中读取 count 个字节到缓冲区 buf 中。
返回值:
成功时返回读到的字节数,0表示读到文件末尾了。如果返回字节数小于指定的字节数,不一定出错,有可能文件就剩这么多数据了。出错时返回 -1,并设置 errno 为合适值。
示例:
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{
int fd, res;
char buf[20];
char name[] = "666.txt";
fd = open(name, O_RDONLY);
res = read(fd, buf, sizeof(buf));
printf("%s\n", buf);
return 0;
}
write 函数
头文件:
#include <unistd.h>
函数原型:
ssize_t write(int fd, const void *buf, size_t count);
write 函数会尝试从缓冲区 buf 中读取 count 个字节,写到文件描述符 fd 中。
返回值:
成功时返回写入的字节数。如果返回字节数小于指定的字节数,不一定出错,有可能是磁盘满了。出错时返回 -1,并设置 errno 为合适值。
示例:
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main()
{
int fd, res;
char str[] = "hello world!";
char name[] = "666.txt";
fd = open(name, O_WRONLY); // 必须要有写权限
res = write(fd, str, sizeof(str));
printf("%d\n", res);
return 0;
}
fsync 函数
磁盘读写速度很慢,为了优化性能,Linux 在写磁盘时,加了一层缓存,数据攒够一定数量或程序结束后才将数据写入磁盘。write 函数每次只是将数据写到缓存,如果需要强制其写入磁盘,需要使用 fsync 命令。
头文件:
#include <unistd.h>
函数原型:
int fsync(int fd);
int fdatasync(int fd);
返回值:
操作成功返回 0,否则返回 -1,同时设置全局变量 errno。
sync() 函数同步整个系统修改过的缓存数据,而 fsync() 则只针对一个具体文件。
lseek 函数
有的设备支持随机读写文件,例如磁盘,而有的则只支持顺序读写,例如管道、套接字和 FIFO。支持随机读写的设备,可以通过 lseek 函数移动读写位置。之后的读写操作,将会从这个新位置开始。
头文件:
#include <unistd.h>
函数原型:
off_t lseek(int fd, off_t offset, int whence);
参数:
- offset:目标位置,其偏移的参照点,由第三个参数 whence 决定
- whence:有效值是 SEEK_SET、SEEK_CUR、SEEK_END,含义如下:
- SEEK_SET 设置新的读写位置为从文件开头算起,偏移 offset 字节;
- SEEK_CUR 设置新的读写位置为从当前所在的位置算起,偏移 offset 字节,正值表示往文件尾部偏移,负值表示往文件头部偏移;
- SEEK_END 设置新的读写位置为从文件结尾算起,偏移 offset 字节,正值表示往文件尾部偏移,负值表示往文件头部偏移。
返回值:
操作成功则返回新的读写位置,否则返回 -1。按顺序读写的文件不支持 lseek 操作,对这类文件调用 lseek(),将返回-1,且 errno=ESPIPE。
如果只是想测试设备是否支持该操作,可以执行这个语句,只有返回值大于 -1,就是支持的:
res = lseek(fd, 0, SEEK_CUR);
示例:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd, res;
char name[] = "666.txt";
char buf[20];
fd = open(name, O_RDONLY);
lseek(fd, 5, SEEK_SET);
res = read(fd, buf, 10);
printf("%s\n", buf);
return 0;
}
ioctl 函数
ioctl 是文件 IO 的杂项函数,可以实现一些设备相关的操作,例如修改寄存器的值。
头文件:
#include <sys/ioctl.h>
函数原型:
int ioctl(int d, int request, ...);
参数:
- d:打开文件的描述符
- request:文件的操作命令,参数值决定后面的参数含义,
...
表示从参数是可选的、类型不确定的。不同的文件,cmd 一般不同,比如嵌入式系统中的设备文件,蜂鸣器(BUZZER)和模数转换(ADC)。
返回值:
操作成功则返回0,否则返回 -1。部分设备可能返回正数表示参数。
stat 和 lstat 函数
跟 Linux 终端中的 stat 命令作用一样,stat 函数也用来查看文件属性。
# stat tmux-client-14353.log
File: ‘tmux-client-14353.log’
Size: 54 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 256424 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2018-11-17 14:56:09.963358724 +0800
Modify: 2018-11-17 14:56:16.992381417 +0800
Change: 2018-11-17 14:56:16.992381417 +0800
Birth: -
头文件及函数原型:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf); /* 查看 path 文件名指向的文件的属性,放到 buf 中*/
int fstat(int fd, struct stat *buf); /* 文件名变成文件描述符 */
int lstat(const char *path, struct stat *buf); /* 文件名是一个符号链接,查看这个符号链接的属性 */
返回值:
struct stat {
dev_t st_dev; /* 文件的设备编号,ID of device containing file */
ino_t st_ino; /* Inode 编号,inode number */
mode_t st_mode; /* 文件类型和权限,protection */
nlink_t st_nlink; /* 硬链接个数,number of hard links */
uid_t st_uid; /* 用户ID,user ID of owner */
gid_t st_gid; /* 组ID,group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* 文件大小,total size, in bytes */
blksize_t st_blksize; /* 块大小,blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* 最后一次修改时间,time of last modification */
time_t st_ctime; /* time of last status change */
};
示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
int main()
{
char name[] = "666.txt";
struct stat buf;
int ret = stat(name, &buf);
if (ret < 0)
{
printf("get file status error, errorno is:%d ", errno);
}
printf("UID is: %d\nGID is: %d\nsize is: %d\n", (int)buf.st_uid, (int)buf.st_gid, (int)buf.st_size);
return 0;
}
access 函数
检查当前用户对文件是否具有某个权限,还可以判断文件是否存在。
头文件及函数原型:
#include <unistd.h>
int access(const char *pathname, int mode);
参数:
- mode 支持4个参数:
- R_OK:是否有读权限
- W_OK:是否有写权限
- X_OK:是否有执行权限
- F_OK:判断文件是否存在
示例:
#include <stdio.h>
#include <unistd.h>
int main()
{
char name[] = "666.txt";
int ret = access(name, R_OK);
if (ret == -1)
{
printf("you can not read \"%s\"\n", name);
}
printf("you can read \"%s\"\n", name);
}
chmod 和 chown 函数
修改文件权限,修改所有者和所属用户。
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
rename 函数
改变文件的名字或路径。
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
getcwd 函数
获取当前的工作目录。可以通过返回值或入参 buf 返回当前的绝对路径。
#include <unistd.h>
char *getcwd(char *buf, size_t size);
char *getwd(char *buf); /* 已经废弃 */
char *get_current_dir_name(void);
chdir 和mkdir 函数
更改当前目录,创建新目录。
#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);
示例:
#include <unistd.h>
int main()
{
char name[] = "new_dir";
char buf[100];
mkdir(name);
chdir(name);
char *pwd = getcwd(buf, 100);
printf("%s\n", pwd);
printf("%s\n", buf);
return 0;
}
opendir 和 readdir 函数
打开目录,读目录。man 2 opendir
没找到描述,最好别用,可以用封装好的 C 库函数。
int readdir(unsigned int fd, struct old_linux_dirent *dirp, unsigned int count);
dup 和 dup2 函数
复制文件描述符。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
fcntl 函数
修改文件描述符。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
综合示例
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char* argv[])
{
int fd, res;
char str[] = "hello world!";
char buf[20] = {0};
char name[] = "666.txt";
fd = open(name, O_WRONLY);
if (fd < 0)
{
printf("open file %s failed, errorno = %d\n", name, errno);
return -1;
}
res = write(fd, str, sizeof(str));
printf("write %d bytes to \"%s\"\n", res, name);
fsync(fd);
close(fd);
fd = open(name, O_RDONLY);
if (fd < 0) return -1;
res = read(fd, buf, sizeof(buf));
printf("read output is:\n%s\n", buf);
printf("read %d bytes from \"%s\"", res, name);
return 0;
}
【Linux 应用编程】文件IO操作 - 常用函数的更多相关文章
- Linux系统编程--文件IO操作
Linux思想即,Linux系统下一切皆文件. 一.对文件操作的几个函数 1.打开文件open函数 int open(const char *path, int oflags); int open(c ...
- Linux学习记录--文件IO操作相关系统编程
文件IO操作相关系统编程 这里主要说两套IO操作接口,各自是: POSIX标准 read|write接口.函数定义在#include<unistd.h> ISO C标准 fread|fwr ...
- linux系统编程--文件IO
系统调用 什么是系统调用: 由操作系统实现并提供给外部应用程序的编程接口.(Application Programming Interface,API).是应用程序同系统之间数据交互的桥梁. C标准函 ...
- Linux文件IO操作
来源:微信公众号「编程学习基地」 目录 文件操作 Linux文件类型 Linux文件权限 修改文件权限 Linux error 获取系统调用时的错误描述 打印错误信息 系统IO函数 open/clos ...
- php中文件操作常用函数有哪些
php中文件操作常用函数有哪些 一.总结 一句话总结:读写文件函数 判断文件或者目录是否存在函数 创建目录函数 file_exists() mkdir() file_get_content() fil ...
- linux文件IO操作篇 (一) 非缓冲文件
文件IO操作分为 2 种 非缓冲文件IO 和 缓冲文件IO 它们的接口区别是 非缓冲 open() close() read() write() 缓冲 fopen() fclose() fread() ...
- 树莓派学习笔记——使用文件IO操作GPIO SysFs方式
0 前言 本文描写叙述假设通过文件IO sysfs方式控制树莓派 GPIO端口.通过sysfs方式控制GPIO,先訪问/sys/class/gpio文件夹,向export文件写入GPIO编号, ...
- 9.2 Go 文件IO操作
9.2 Go 文件IO操作 1.1.1. bufio包 带有缓冲区的IO读写操作,用于读写文件,以及读取键盘输入 func main() { //NewReader返回一个结构体对象指针 reader ...
- 文件IO操作
前言 本文介绍使用java进行简单的文件IO操作. 操作步骤 - 读文件 1. 定义一个Scanner对象 2. 调用该对象的input函数族进行文件读取 (参见下面代码) 3. 关闭输入流 说明:其 ...
随机推荐
- for迭代
for迭代 Python中最简单的循环机制是 while,打开交互式解释器,执行1到的5的循环 >>> count=1 >>> while count<=5: ...
- Qualcomm_Mobile_OpenCL.pdf 翻译-5-性能优化的概述
这章提供了一个OpenCL应用程序优化的总体概述.更多的细节将会在接下来的章节中找到. 注意:OpenCL程序的优化是具有挑战性的.相比初始的程序开发工作,经常需要做更多的工作. 5.1 性能移植性 ...
- java面试(Web相关)06
1.JSP 和 servlet 有什么区别? JSP 是 servlet 技术的扩展,本质上就是 servlet 的简易方式.servlet 和 JSP 最主要的不同点在于,servlet 的应用逻辑 ...
- python 反射、动态导入
1. 反射 hasattr(obj,'name') # 判断对象中是否含有字符串形式的方法名或属性名,返回True.False getattr(obj,'name',None) ...
- 搭建团队协作办公wiki (confluence)
搭建环境 操作系统:centos7 数据库:mysql 一.准备工作 下载软件:atlassian-confluence-6.7.1-x64.bin wget https://downloads.at ...
- 动态列表+动态样式(vue双向绑定)
先上效果图 注:下面的几个值可以从其他地方获取,这边演示我是写死的 在上逻辑图 接着上代码template部分 <template> <div > <div> &l ...
- 25.复杂链表的复制(python)
题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用,否 ...
- shell练习--关于二维数组的实现问题
关于二维数组的实现问题:通过查询网友们的记录,可知有两个方法 方法1:通过通过2次读取赋值,来获取二维数组的方法. a=('1 2 3' '4 5 6' '7 8 9') for i in " ...
- CentOS8 中文输入法
CentOS8发布了,安装了下试试,结果发现中文输入法调不出来. 系统安装完成后,在系统[设置]的[Region&Language]里的[输入源]里可以添加汉语输入源,但是不能打中文字. 下面 ...
- 安装原版 Windows 7 后需要安装的微软更新 和 必备系统组件
Windows 7 SP1 和 Windows Server 2008 R2 SP1 更新历史记录 https://support.microsoft.com/zh-cn/help/4009469 微 ...