《APUE》笔记-第三章-文件I/O

1.引言

本章讲解的函数有:open、read、write、lseek、close、dup、fcntl、sync、fsync、ioctl都是系统调用,都是不带缓冲的I/O

2.文件描述符

open、create返回文件描述符fd,其他函数将fd作为参数。
  1 #include <stdio.h>
  2 #include <unistd.h>
  3
  4 int main()
  5 {
  6         printf("STDIN_FILENO = %d\n", STDIN_FILENO);
  7         printf("STDOUT_FILENO = %d\n", STDOUT_FILENO);
  8         printf("STDERR_FILENO = %d\n", STDERR_FILENO);
  9         long openmax;
 10         openmax = sysconf(_SC_OPEN_MAX);
 11         printf("_SC_OPEN_MAX = %ld\n", openmax);
 12         return 0;
 13 }
结果:

分析:
1.#include <unistd.h>//for STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO、sysconf()
2.文件描述符:STDIN_FILENO = 0,与标准输入关联;STDOUT_FILENO = 1,与标准输出关联;STDERR_FILENO = 2,与标准错误关联;
3.long sysconf(int name);
OPEN_MAX = sysconf(_SC_OPEN_MAX);
文件描述符范围:0~OPEN_MAX-1

3.open和openat

#include <fcntl.h>
int open(const char *path, int flag, .../*mode_t mode*/);
int openat(int fd, const char *path, int flag, .../*mode_t mode*/);
返回值:成功,返回文件描述符;出错,返回-1

  1 #include <stdio.h>
  2 #include <fcntl.h>
  3 #include <unistd.h>
  4 #include <sys/stat.h>
  5
  6 int main()
  7 {
  8         int fd1 = open("tempfile", O_RDONLY);
  9         int fd2 = open("/home/zxin/ch3/tempfile", O_RDONLY);
 10         int fd3 = openat(AT_FDCWD, "tempfile", O_RDONLY);
 11         int fd4 = openat(fd3, "/home/zxin/ch3/tempfile", O_RDONLY);
 12         //int fd5 = open("newfile", O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
 13         int fd5 = open("newfile", O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
 14         int fd6 = open("tempfile", O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
 15
 16         printf("fd1=%d\n", fd1);
 17         printf("fd2=%d\n", fd2);
 18         printf("fd3=%d\n", fd3);
 19         printf("fd4=%d\n", fd4);
 20         printf("fd5=%d\n", fd5);
 21         printf("fd6=%d\n", fd6);
 22         printf("NAME_MAX=%ld\n", pathconf("/", _PC_NAME_MAX));
 23         return 0;
 24 }
结果:

分析:
1.#include <fcntl.h>//for flag;   #include <sys/stat.h>//for mode_t mode
2.open中路径名可以是绝对路径或者相对于当前工作目录的相对路径
3.O_RDONLY=0;O_WRONLY=1;O_RDWR=2;
4.若文件已存在,则使用 O_CREAT | O_EXCL 会出错
5.使用open创建新文件的方法,open("newfile", O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); //同时使用 O_CREAT和O_EXCL确保创建的是新文件,而不会覆盖以前的文件
6.long sysconf(int name);
  long pathconf(const char *pathname, int name);
  long fpathconf(int fd, int name);

4.creat

#include <fcntl.h>
int creat(const char *path, mode_t mode);
返回值:成功,返回为只写打开的文件描述符;错误,返回-1

  1 #include <stdio.h>
  2 #include <fcntl.h>
  3
  4 int main()
  5 {
  6         int fd1 = creat("cfile", S_IRWXU);
  7         int fd2 = open("cfile2", O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU);
  8
  9         printf("fd1=%d\n", fd1);
 10         printf("fd2=%d\n", fd2);
 11         return 0;
 12 }
结果:

分析:
open("pathname", O_WRONLY|O_CREAT|O_TRUNC, mode)等效于creat("pathname", mode)

5.close

#include <unistd.h>
int close(int fd);
返回值:成功,返回0;错误,返回-1

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <fcntl.h>
  4
  5 int main()
  6 {
  7         int fd1 = open("tempfile", O_RDONLY);
  8         int fd2 = 10;
  9         int returnval, returnval2;
 10         printf("returnval=%d\n", close(fd1));
 11         printf("returnval2=%d\n", close(fd2));
 12         return 0;
 13 }
结果:

分析:
当一进程终止时,内核自动关闭该进程打开的所有文件。

6.lseek

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
返回值:成功,返回新的文件偏移量;错误,返回-1
测试一个文件能否设置偏移量,程序如下:
1 #include <stdio.h>
  2 #include <unistd.h>
  3
  4 int main()
  5 {
  6         if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
  7                 printf("cannot seek\n");
  8         else
  9                 printf("seek ok\n");
 10
 11         return 0;
 12 }
结果:
创建一有空洞的文件,程序如下:
  1 #include <stdio.h>
  2 #include <fcntl.h>
  3 #include <unistd.h>
  4 #include <sys/stat.h>
  5
  6 int main()
  7 {
  8         char buf[]="abcdefghij";
  9         char buf2[]="ABCDEFGHIJ";
 10
 11         int fd = creat("file.hole", S_IRWXU);
 12         int n = write(fd, buf, 10);
 13         if (n != 10)
 14         {
 15                 perror("write() error");
 16                 exit(-1);
 17         }
 18
 19         if (lseek(fd, 16384, SEEK_SET) == -1)
 20         {
 21                 perror("lseek() error");
 22                 exit(-1);
 23         }
 24
 25         if (write(fd, buf2, 10) != 10)
 26         {
 27                 perror("write() error");
 28                 exit(-1);
 29         }
 30
 31         return 0;
 32 }
结果:



7.read和write

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
返回值:读到的字节数,若已到文件尾,返回0;出错,返回-1
ssize_t write(int fd, const void *buf, size_t nbytes);
返回值:成功,返回已写的字节数;出错,返回-1

使用read和write复制一个文件,程序如下:
  1 #include <stdio.h>
  2 #include <unistd.h>
  3
  4 #define BUFSIZE 4096
  5
  6 int main()
  7 {
  8         char buf[BUFSIZE];
  9         int n;
 10         while ((n = read(STDIN_FILENO, buf, BUFSIZE)) > 0)
 11         {
 12                 if (write(STDOUT_FILENO, buf, n) != n)
 13                 {
 14                         perror("write() error");
 15                         exit(-1);
 16                 }
 17         }
 18         if (n < 0)
 19         {
 20                 perror("read() error");
 21                 exit(-1);
 22         }
 23         return 0;
 24 }
结果:


分析:
对于read,有多种情况会导致读到的字节数少于要求读的字节数(见书);但对于write来说,其返回值通常与要求写的字节数相等,否则表示出错;因此,read和write是否成功调用的判断条件也不相同。

8.文件共享

如果两个进程各自独立的打开了同一个文件,则有下图的关系:


9.原子操作

定义:原子操作指的是多步组成的一个操作。执行一个原子操作时,该原子操作里面的多步操作要么执行完所有步骤,要么一个步骤也不执行。内核不可能只执行原子操作中的某些步骤后又切换到其他进程去执行了。要么一个也不执行,要么全部执行完。
举例:lseek函数和O_APPEND标志
两个进程A,B要读同一个文件的偏移量,然后写该文件,假设当前偏移量为100。
用lseek:A用lseek后得到offset为100-->>进程切换到B-->>B用lseek后也得到offset为100-->>进程又切换到A-->>A写数据10个字节(该文件的100到110个字节被A写上数据)-->进程切换到B-->>B写数据10个字节(此时B不知道A已经写到110了,所以B还是从100开始写,导致A写的数据被B覆盖)
用O_APPEND标志:A读取文件偏移量然后写数据(原子操作)-->>B读取文件偏移量然后写数据(原子操作)
可见用O_APPEND后,读取偏移量和写数据这两步合成一个原子操作了,不会发生lseek中途进程切换的现象。

#include <unistd.h>
ssize_t pread(int fd,  void *buf, size_t nbytes, off_t offset);
返回值:读到的字节数,若已到文件尾,返回0;出错,返回-1
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
返回值:成功,返回已写的字节数;出错,返回-1

这两个函数都是原子性的定位并执行I/O

10.dup和dup2

int dup(int fd);
int dup2(int fd, int fd2);
返回值:成功,返回新的文件描述符;出错,返回-1

复制打开的文件描述符和标准输入描述符,程序如下:
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <fcntl.h>
  4
  5 int main()
  6 {
  7         char buf[] = "apue";
  8         int fd = open("tempfile", O_RDWR | O_TRUNC);
  9         int newfd = dup(fd);
 10         int newfd2 = dup2(STDIN_FILENO, 10);
 11
 12         printf("fd=%d, newfd=%d, newfd2=%d\n", fd, newfd, newfd2);
 13         return 0;
 14 }
结果:


分析:
返回的文件描述符与参数fd共享同一个文件表项,如下图所示:


11.sync、fsync、fdatasync

#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
返回值:成功,返回0;出错,返回-1

void sync(void);

功能:将内核中高速缓存写入磁盘。
sync:将修改过的块缓冲区排入写队列就返回,不等待写完成。
fsync、fdatasync:等待往磁盘上写完成;针对特定文件描述符;fdatasync只影响文件的数据部分。

12.fcntl

# include <fcntl.h>
int fcntl(int fd, int cmd, .../*arg*/);
返回值:成功,依赖于cmd;错误,返回-1
功能:改变已打开文件的属性


测试并设置文件的状态标识,程序如下:
 

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <fcntl.h>
  4
  5 void print_val(int val)
  6 {
  7
  8         switch (val & O_ACCMODE)
  9         {
 10                 case O_RDONLY:
 11                         printf("read only\n");
 12                         break;
 13                 case O_WRONLY:
 14                         printf("write only\n");
 15                         break;
 16                 case O_RDWR:
 17                         printf("read and write\n");
 18                         break;
 19         }
 20         if (val & O_APPEND)
 21                 printf("append\n");
 22         if (val & O_NONBLOCK)
 23                 printf("nonblock\n");
 24 }
 25
 26 int main(int argc, char *argv[])
 27 {
 28         if (argc != 2)
 29         {
 30                 printf("argument error");
 31                 exit(-1);
 32         }
 33         int val = fcntl(atoi(argv[1]), F_GETFL, 0);
 34         print_val(val);
 35
 36         fcntl(atoi(argv[1]), F_SETFL, O_NONBLOCK);
 37         val = fcntl(atoi(argv[1]), F_GETFL, 0);
 38         print_val(val);
 39
 40         return 0;
 41 }

结果:

分析:
测试O_RDONLY、O_WRONLY、O_RDWR要用屏蔽字O_ACCMODE取得访问方式位。


13.ioctl

终端I/O是使用ioctl最多的地方,暂不知道该怎么用,日后补充

14./dev/fd

目录项是名为0,1,2,255的文件,打开文件/dev/fd/n等效于复制描述符n。
linux中的/dev/fd,它把文件描述符映射成指向底层物理文件的符号链接。
在shell中,打开/dev/fd,如下图所示:




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值