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操作 - 常用函数的更多相关文章

  1. Linux系统编程--文件IO操作

    Linux思想即,Linux系统下一切皆文件. 一.对文件操作的几个函数 1.打开文件open函数 int open(const char *path, int oflags); int open(c ...

  2. Linux学习记录--文件IO操作相关系统编程

    文件IO操作相关系统编程 这里主要说两套IO操作接口,各自是: POSIX标准 read|write接口.函数定义在#include<unistd.h> ISO C标准 fread|fwr ...

  3. linux系统编程--文件IO

    系统调用 什么是系统调用: 由操作系统实现并提供给外部应用程序的编程接口.(Application Programming Interface,API).是应用程序同系统之间数据交互的桥梁. C标准函 ...

  4. Linux文件IO操作

    来源:微信公众号「编程学习基地」 目录 文件操作 Linux文件类型 Linux文件权限 修改文件权限 Linux error 获取系统调用时的错误描述 打印错误信息 系统IO函数 open/clos ...

  5. php中文件操作常用函数有哪些

    php中文件操作常用函数有哪些 一.总结 一句话总结:读写文件函数 判断文件或者目录是否存在函数 创建目录函数 file_exists() mkdir() file_get_content() fil ...

  6. linux文件IO操作篇 (一) 非缓冲文件

    文件IO操作分为 2 种 非缓冲文件IO 和 缓冲文件IO 它们的接口区别是 非缓冲 open() close() read() write() 缓冲 fopen() fclose() fread() ...

  7. 树莓派学习笔记——使用文件IO操作GPIO SysFs方式

    0 前言     本文描写叙述假设通过文件IO sysfs方式控制树莓派 GPIO端口.通过sysfs方式控制GPIO,先訪问/sys/class/gpio文件夹,向export文件写入GPIO编号, ...

  8. 9.2 Go 文件IO操作

    9.2 Go 文件IO操作 1.1.1. bufio包 带有缓冲区的IO读写操作,用于读写文件,以及读取键盘输入 func main() { //NewReader返回一个结构体对象指针 reader ...

  9. 文件IO操作

    前言 本文介绍使用java进行简单的文件IO操作. 操作步骤 - 读文件 1. 定义一个Scanner对象 2. 调用该对象的input函数族进行文件读取 (参见下面代码) 3. 关闭输入流 说明:其 ...

随机推荐

  1. Docker 镜像 容器 仓库

    Docker 包括三个基本概念镜像(Image)容器(Container)仓库(Repository) Docker 镜像 Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序.库.资 ...

  2. 神奇的AI:将静态图片转为3D动图

    近日我们从外媒获得消息,位于莫斯科的三星AI中心和Skolkovo科学技术研究所的研究人员发表了一篇新论文,详细介绍了从单个静止人像照片生成3D动画人像的创建.与此前能够生成照片般逼真肖像的人工智能A ...

  3. 微信开发企业支付到银行卡PHP

    微信开发企业支付到银行卡 功能详解 不会的朋友可以加我QQ:344902511先发个微信支付官方链接你查看https://pay.weixin.qq.com/wiki/doc/api/tools/mc ...

  4. MySQL Select查询

    1. 基本语法: SELECT {* | <字段列名>} [ FROM <表 1>, <表 2>… [WHERE <表达式> [GROUP BY < ...

  5. 移动端布局基础viewport

    划重点 手机屏幕相对着桌面浏览器小,传统网页的设计在手机上体验糟糕 Apple 在移动版 Safari 中定义了 viewport meta 标签(如果没记错最早提出的话),用于创建一个虚拟窗口(la ...

  6. Django初使用

    目录 一.Django初使用 1. 静态文件配置 (1)静态文件配置步骤 2. form表单的get和post提交方式 (1)get (2)post (3)注意 3. views文件中的request ...

  7. 【leetcode】Largest Plus Sign

    题目如下: In a 2D grid from (0, 0) to (N-1, N-1), every cell contains a 1, except those cells in the giv ...

  8. 解析binlog生成MySQL回滚脚本

    如果数据库误操作想恢复数据.可以试试下面这个脚本.前提是执行DML操作. #!/bin/env python #coding:utf-8 #Author: Hogan #Descript : 解析bi ...

  9. 私有ip地址知多少?

    1.私有ip的由来 在现在的网络中,ip地址分为公网ip地址和私有ip地址.公网ip是在Internet中使用的ip地址,而私有ip地址是在局域网中使用,在Internet上不使用. 由于我们目前使用 ...

  10. Linux基础教程 linux无密码ssh登录设置

      概述 在一些常用设备之间ssh, scp,不用输入密码可以节省不少时间. 生成密钥 先看本地是否有密钥,如果有,则不用生成,否则会影响到以前打通的设备. 复制代码代码如下: 没有则用 ssh-ke ...