本文主要介绍了如下内容:

C标准库函数与系统函数的关系

进程控制块

文件描述符

系统调用:open、close、read、write、lseek、fcntl和ioctl

先导概念

C标准库函数与系统函数的关系

API层次如图所示:

API层次

API调用顺序

由上往下(用户态 -> 内核态)的顺序依次是:

C标准库函数:调用系统库函数(即 系统调用);

系统调用:即操作系统的应用层API,调用内核层API;

内核层API: 调用具体的驱动层API(在Linux中一般以sys_开头);

驱动层函数:直接控制硬件设备。

以调用fwrite()函数将文件内容显示在终端为例,fwrite()函数将调用write()系统调用,而write()系统调用的实现则是调用内核态的sys_write()函数,由sys_write()来判断具体调用哪个驱动函数来访问硬件设备。

当然,对于Linux操作系统而言,还多了一层VFS(virtual File System,虚拟文件系统)层:

write()系统调用将来自用户空间的数据流,首先通过VFS的通用系统调用,然后通过文件系统的特殊写法,最后写入物理介质中。

各API在缓冲区上的不同之处

fopen():每打开一个文件,都会对应一个单独的缓冲区;

open():无缓冲区;

sys_open:有缓冲区,但是由所有打开的文件共用。

关于缓冲区的刷新方式:

刷新C标准缓冲区

缓冲区满,自动刷新;

手动调用fflush()函数刷新;

使用fclose()函数关闭文件时刷新;

程序正常结束后缓冲区自动刷新。

刷新内核缓冲区

由一个守护进程定时刷新。

PCB和文件描述符fd

PCB(process control block,进程控制块)在Linux源码中的实现即task_struct结构体,位于/include/linux/sched.h文件中。该结构体在Linux中被称为进程描述符(process descriptor)。 其部分结构如下(linux kernel 版本为4.4.36):

1380 struct task_struct {

1381 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */

1382 void *stack;

1383 atomic_t usage;

1384 unsigned int flags; /* per process flags, defined below */

1385 unsigned int ptrace;

1386

1387 #ifdef CONFIG_SMP

1388 struct llist_node wake_entry;

1389 int on_cpu;

1390 unsigned int wakee_flips;

1391 unsigned long wakee_flip_decay_ts;

1392 struct task_struct *last_wakee;

1393

1394 int wake_cpu;

1395 #endif

1396 int on_rq;

1483 pid_t pid;

1484 pid_t tgid;

Linux内核把进程的列表存放在任务队列(task list)中,该队列是一个双向循环链表,链表中的每一项都是一个task_struct结构体。

在Linux内核中,每一个进程都有一个PCB来管理,每一个PCB中都有一个指向files_struct结构体的指针:

1564 /* open file information */

1565 struct files_struct *files;

可以看到,task_struct结构体中的files是个指针(充当目录项的角色),指向files_struct结构体。而files_struct结构体是一张文件描述符表(实际上就是一个整形数组,里面存放的是诸如0、 1、 2这样的文件描述符,文件描述符即一些非负整数),这些文件描述符指向真正的设备文件,包括磁盘文件、显示屏文件等所有文件。

文件描述符struct files_struct 源码:

(位于linux-4.4.36/include/linux/fdtable.h中)

43 /*

44 * Open file table structure

45 */

46 struct files_struct {

47 /*

48 * read mostly part

49 */

50 atomic_t count; /* 该结构体的引用计数 */

51 bool resize_in_progress;

52 wait_queue_head_t resize_wait;

53

54 struct fdtable __rcu *fdt;

55 struct fdtable fdtab;

56 /*

57 * written part on a separate cache line in SMP

58 */

59 spinlock_t file_lock ____cacheline_aligned_in_smp;

60 int next_fd;

61 unsigned long close_on_exec_init[1];

62 unsigned long open_fds_init[1];

63 unsigned long full_fds_bits_init[1];

64 struct file __rcu * fd_array[NR_OPEN_DEFAULT]; /* 缺省的文件对象数组 */

65 };

关系图:

文件句柄关系

文件‘开’ ‘关’ ‘读’ ‘写’的系统接口

open()

功能:打开或者创建(如果文件不存在)一个文件。

每打开一个文件,操作系统内核(kernel)就会在内存中新建一个files_struct结构体。

在同一个进程中 多次打开同一个文件,内核也会在内存中分别新建不同的files_struct结构体(由不同的文件描述符映射)。因此,每次打开的文件在使用完之后一定要及时关闭,否则可能会引起内存泄漏。

声明:

NAME

open - open and possibly create a file

SYNOPSIS

#include

#include

#include

int open(const char *pathname, int flags);

int open(const char *pathname, int flags, mode_t mode);

返回值:

成功:返回新分配的文件描述符;

出错:则返回-1,并设置errno;

close()

功能:关闭一个打开的文件,一般与open()成对使用。

每调用一次close(fd),实际上是将该文件描述符fd所指向的files_struct结构体中的引用计数count值减一。当引用计数值减为0时,操作系统内核(kernel)才真正关闭该文件。

通过调用dup/dup2系统调用可使files_struct结构体中的引用计数count值加一。具体是dup/dup2新生成一个文件描述符newfd,并使其指向旧文件描述符oldfd所指向的files_struct结构体,即这两个文件描述符共用一个files_struct结构体。

声明:

NAME

close - close a file descriptor

SYNOPSIS

#include

int close(int fd);

返回值:

成功:返回0;

出错:则返回-1,并设置errno;

read()

功能: 从打开的设备或文件中读取数据。

声明:

NAME

read - read from a file descriptor

SYNOPSIS

#include

ssize_t read(int fd, void *buf, size_t count);

返回值:

成功:返回读取的字节数;

出错:则返回-1,并设置errno;

如果在调read之前已到达文件末尾,则这次read返回0。

write()

功能:从内存地址buf开始,向打开的文件写入count字节(byte)的数据。

声明:

NAME

write - write to a file descriptor

SYNOPSIS

#include

ssize_t write(int fd, const void *buf, size_t count);

返回值:

成功:返回写入的字节数;

出错:返回-1,并设置errno。

注意:

在向常规文件进行写操作时,write函数的返回值通常等于请求写的字节数count,而向终端设备或网络设备进行写操作时则不一定。

Demo:mycp.c

程序功能描述:模仿cp命令,将一个文件中的内容复制到一个新的文件之中。

code:

#include

#include

#include

#include

#include

#include

#define SIZE 8192

int main(int argc, char *argv[])

{

int fd_src, fd_des, len;

char buf[SIZE];

/* 参数输入太少,不符合要求,打印命令使用提示信息并退出 */

if (argc < 3) {

printf("Usage: ./mycp src_file des_file\n");

exit(1);

}

/* 打开源文件 */

fd_src = open(argv[1], O_RDONLY);

if (fd_src == -1) {

printf("Openning file %s failed...\n", argv[1]);

exit(-1);

}

/* 新建目标文件 */

fd_des = open(argv[2], O_CREAT | O_WRONLY | O_TRUNC, 0664);

if (fd_des == -1) {

printf("Creating file %s failed...\n", argv[2]);

exit(-1);

}

/* 读取源文件中的内容,然后写入目标文件之中 */

while ( (len = read(fd_src, buf, sizeof(buf))) > 0 ) {

write(fd_des, buf, len);

}

/* 关闭文件 */

close(fd_src);

close(fd_des);

return 0;

}

test

slot@slot-ubt:~/test$ gcc mycp.c -o mycp

slot@slot-ubt:~/test$ cat aa

Hello, this is my cp cmd.

Welcome to use...

slot@slot-ubt:~/test$ cat bb

cat: bb: No such file or directory

slot@slot-ubt:~/test$ ./mycp aa bb

slot@slot-ubt:~/test$ cat bb

Hello, this is my cp cmd.

Welcome to use...

slot@slot-ubt:~/test$

lseek()

功能:移动打开的文件的读写指针的位置。

每个打开的文件都记录着当前读写指针的位置,打开文件时读写位置是0,表示文件开头。通常,读写多少个字节,就会将读写位置往后移动多少个字节。但有一个例外,如果以O_APPEND(追加)方式打开,则每次写操作都会在文件末尾追加数据,然后将读写位置移到新的文件末尾。

声明:

NAME

lseek - reposition read/write file offset

SYNOPSIS

#include

#include

off_t lseek(int fd, off_t offset, int whence);

lseek的两个"副作用"示例

demo1. 扩展一个文件

注意:

拓展一个文件,一定要有一个写操作。

code:extend_file.c

#include

#include

#include

#include

#include

int main(void)

{

int fd;

/* 新建一个名为abc的文件 */

fd = open("abc", O_CREAT | O_RDWR);

if (fd < 0) {

perror("Opening file failed: ");

exit(-1);

}

/* 将读写指针移到文件末尾 */

lseek(fd, 0x1000, SEEK_SET);

/* 追加写一个字节到文件中去

* string "a" will be translated to an addr

* od -tcx see file abc

*/

write(fd, "a", 1);

close(fd);

return 0;

}

errno是个用户态的全局变量,声明在头文件/usr/include/errno.h中 :

45 #ifndef errno

46 extern int errno;

47 #endif

Linux下的错误码可以查阅文件:/usr/include/asm-generic/errno-base.h (部分展示如下):

1 #ifndef _ASM_GENERIC_ERRNO_BASE_H

2 #define _ASM_GENERIC_ERRNO_BASE_H

3

4 #define EPERM 1 /* Operation not permitted */

5 #define ENOENT 2 /* No such file or directory */

6 #define ESRCH 3 /* No such process */

7 #define EINTR 4 /* Interrupted system call */

8 #define EIO 5 /* I/O error */

9 #define ENXIO 6 /* No such device or address */

10 #define E2BIG 7 /* Argument list too long */

11 #define ENOEXEC 8 /* Exec format error */

12 #define EBADF 9 /* Bad file number */

13 #define ECHILD 10 /* No child processes */

14 #define EAGAIN 11 /* Try again */

15 #define ENOMEM 12 /* Out of memory */

16 #define EACCES 13 /* Permission denied */

perror()函数将打印用户自定义信息和errno后面对应的注释信息,其声明为:

NAME

perror - print a system error message

>

SYNOPSIS

#include

>

void perror(const char *s);

>

#include

>

const char * const sys_errlist[];

int sys_nerr;

int errno;

test:

slot@slot-ubt:~/test$ gcc extend_file.c -o exf

slot@slot-ubt:~/test$ ./exf

slot@slot-ubt:~/test$ od -txc abc

0000000 00000000 00000000 00000000 00000000

\0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0

*

0010000 00000061

a

0010001

demo2. 获取文件的大小

方法:将指针移到文件末尾,然后输出返回值,该值即文件大小。

code: see_file_size.c

#include

#include

#include

#include

#include

int main(void)

{

int fd;

fd = open("abc", O_RDWR);

if (fd < 0) {

perror("Opening file failed: ");

exit(-1);

}

/* print file size */

printf("abc size is: %lld\n", lseek(fd, 0, SEEK_END));

close(fd);

return 0;

}

test:

slot@slot-ubt:~/test$ gcc see_file_size.c -o fsize

slot@slot-ubt:~/test$ ./fsize

abc size is: 4097

slot@slot-ubt:~/test$ ls -l abc

-rwxrwxrwx 1 slot staff 4097 12 14 19:40 abc

fcntl()

功能: 获取或者设置已打开文件的访问属性。

声明:

NAME

fcntl - manipulate file descriptor

SYNOPSIS

#include

#include

int fcntl(int fd, int cmd, ... /* arg */ );

demo:

改变文件的状态标志位为非阻塞状态

code: test_fcntl.c

#include

#include

#include

#include

#include

#include

int main()

{

char buf[10];

int n;

int flags;

/* get file flag */

flags = fcntl(STDIN_FILENO, F_GETFL);

/* change file flags to nonblock */

flags |= O_NONBLOCK;

if (fcntl(STDIN_FILENO, F_SETTL, flags) == -1) {

perror("change file flag failed: ");

exit(1);

}

try_again:

n = read(STDIN_FILENO, buf, 10);

if (n < 0) {

if (errno == EAGAIN) {

sleep(1);

write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));

goto try_again;

}

perror("read stdin failed: ");

exit(1);

}

write(STDOUT_FILENO, buf, n);

return 0;

}

test:

ioctl()

功能:向设备发送控制和配置命令。

有些命令也需要读写一些数据,但这些数据是不能用read/write读写的,称为Out-of-band数据。也就是说,read/write读写的数据是in-band数据,是I/O操作的主体,而ioctl命令传送的是控制信息,其中的数据是辅助的数据。

例如,在串口线上收发数据通过read/write操作,而串口的波特率、校验位、停止位则通过ioctl来设置;A/D转换(模数转换)的结果通过read读取,而A/D转换的精度和工作频率则通过ioctl设置。

声明:

NAME

ioctl - control device

SYNOPSIS

#include

int ioctl(int fd, unsigned long request, ...);

fd是某个设备的文件描述符,request是ioctl的命令,可变参数取决于request,通常是一个指向变量或结构体的指针。

若出错,则返回-1;若成功,则返回其他值。返回值也取决于request。

demo: 获取终端窗口的大小

code: get_tty_size.c

#include

#include

#include

#include

int main(void)

{

struct winsize size;

/* 不是终端设备文件则退出 */

if (isatty(STDOUT_FILENO) == 0)

exit(1);

/* 通过 ioctl() 获取终端窗口的大小 */

if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) {

perror("ioctl TIOCGWINSZ error");

exit(1);

}

/* 打印终端窗口的长和宽 */

printf("%d rows, %d columns\n", size.ws_row, size.ws_col);

return 0;

}

test:

(测试结果依赖于当前终端的窗口大小)

slot@slot-ubt:~/test$ gcc get_tty_size.c -o ttysize

slot@slot-ubt:~/test$

slot@slot-ubt:~/test$ ./ttysize

24 rows, 65 columns

slot@slot-ubt:~/test$

linux下编写打印文件的函数,Linux系统编程笔记-文件IO相关推荐

  1. linux下延时1ms用什么函数,Linux下1ms分辨率定时器推荐方式

    我需要一个在Linux下具有1ms分辨率的计时器刻度.它用于递增计时器值,该计时器值又用于查看是否应触发各种事件.由于glibc的要求,POSIX timerfd_create不是一个选项.我尝试使用 ...

  2. linux编写php,Linux 下编写一个 PHP 扩展

    假设需求 开发一个叫做 helloWord 的扩展. 扩展里有一个函数,helloWord(). echo helloWord('Tom'); //返回:Hello World: Tom 本地环境 P ...

  3. 在Linux下编写Daemon

    在Linux下编写Daemon 转自:http://blog.163.com/prevBlogPerma.do?host=manyhappy163&srl=164476831201071811 ...

  4. 专业的LaTeX: 在Linux下编写高质量的文档

    专业的LaTeX: 在Linux下编写高质量的文档 Linux下的OpenOffice.KWord等字处理软件虽然在功能上与Microsoft Word类似,但目前在易用性和可用性方面仍然存在许多不足 ...

  5. spdlog linux编译出错,Linux下编写Makefile引入第三方库

    Linux下编写Makefile引入第三方库 前言:一直在使用CmakaList 生成Makefile文件,其实很少去写Makefile,但是最近帮朋友处理了一个Makefile引入第三方库的问题,就 ...

  6. 在Linux下编写Daemon(Linux启动流程2)

    在Linux(以Redhat Linux Enterprise Edition 5.3为例)下,有时需要编写Service.Service也是程序,一般随系统启动用户不干预就不退出的程序,可以称为Se ...

  7. Linux下编写贪吃蛇游戏

    Linux下编写贪吃蛇游戏 文章目录 Linux下编写贪吃蛇游戏 前言 一.贪吃蛇代码 二.运行贪吃蛇代码 前言 本程序需要ncurses库,ubuntu下安装ncurses可以执行下面命令: sud ...

  8. texlive - 专业的LaTeX: 在Linux下编写高质量的文档

    dnf install texlive* dnf install texworks dnf install latex* http://www.ibm.com/developerworks/cn/li ...

  9. 在linux下编写动态链接库的步骤: g++ -lc

    引用自http://blog.csdn.net/lwhsyit/article/details/2828306 类似Windows系统中的动态链接库,Linux中也有相应的共享库用以支持代码的复用.W ...

最新文章

  1. Android性能优化之APK优化,内容太过真实
  2. IBM发明世界首个人造神经元,人工智能的底层硬件基石已完成!
  3. LINUX 使用tcgetattr函数与tcsetattr函数控制终端一
  4. 在IIS7中应用Application Request Routing配置反向代理
  5. boost::format模块一些真实的、简单的测试
  6. 团队第二次冲刺第三天
  7. django-多级联动课堂版0912
  8. LGBM使用贝叶斯调参
  9. 沃尔玛痛失世界最大零售商 电商凶猛!
  10. GDC 2006 Microsoft Developer Day Presentations
  11. vue项目首屏加载过久处理笔记
  12. PTA : 函数题 7-3 两个有序链表序列的交集 (20 分)
  13. LayaAir UI 组件 # Clip 切片、ComboBox 下拉框
  14. androidhelper python api_python apihelper
  15. unity导入导出excel的功能
  16. Listary——好用到哭的高效快速搜索工具
  17. idea pull从远程库拉取最新代码
  18. Win10下可连wifi的笔记本共享网络至台式机
  19. Linux环境下配置虚拟ip,方法2:单网卡绑定多IP
  20. Centos 安装 mysql5.7 示例教程

热门文章

  1. 感叹人生!程序员哭诉:36岁被裁员,存款仅余80万!我能过几天?
  2. 应用部署优化方案分享
  3. ElasticSearch学习篇2_Rest格式操作(索引、文档)、文档的简单操作(增、删、改、查)、复杂查询操作(排序、分页、高亮)
  4. html2canvas 浏览器端截屏并下载保存(兼容IE)
  5. 吐槽java之《程序员的呐喊》读后总结
  6. asp.net开发wap程序必备:识别来访手机品牌型号
  7. 图像融合算法(羽化,拉普拉斯金字塔)
  8. 机器学习笔记 - 各领域公开数据集下载
  9. Arduino 读取并数码显示车速传感器数
  10. leetcode452_用最少的箭射气球(python)