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

来源:互联网 发布:powershell for linux 编辑:程序博客网 时间:2024/05/17 01:47

APUE笔记—第三章文件I/O

1. C标准库函数与系统函数的区别

C标准库函数与系统函数的区别

  • 用户程序可以直接访问系统函数
  • 用户程序也可以调用C标准库函数,C标准库函数间接调用系统函数

2. 文件描述符

对于内核而言,所有打开的文件都是通过文件描述符引用,文件描述符是一个非负整数。通常一个进程默认会打开三个文件描述符

STDIN_FILENO    0STDOUT_FILENO   1STDERR_FILENO   2

2.1 打开文件最大个数

命令:ulimit -a

menwen@menwen:~/APUE_code/FILE_IO$ ulimit -a······open files                      (-n) 1024······

打开文件的最大个数为:1024
命令:ulimit -n 4096,可以改变打开文件的最大个数

查看本电脑最大打开的文件个数

menwen@menwen:~/$ cat /proc/sys/fs/file-max 201223

3. 函数open和openat

3.1 open和openat函数

调用open和openat函数可以打开一个或创建一个文件

#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);int openat(int dirfd, const char *pathname, int flags);int openat(int dirfd, const char *pathname, int flags, mode_t mode);//两个函数返回值:若成功,返回文件描述符;若出错,返回-1

path是要打开或创建文件的名字。flags参数可用来说明此函数的多个选
项,用下列一个或多个常量进行“或”运算构成“flag”参数。

必选项 (必须指定一个且只能指定一个)

O_RDONLY        只读打开O_WRONLY        只写打开O_RDWR          可读可写打开

可选项(部分)

O_CREAT        若此文件不存在则创建它。使用此选项时要提供第三个参数mode,表示该文件的访问权限O_EXCL        如果同时指定了O_CREAT,并且文件已存在,则出错返回 O_TRUNC       若文件已存在,并且以只写或可读可写方式打开, 则将其长度截断(trun-cate)为0字节。O_APPEND      每次写都追加到文件尾端  O_NONBLOC     若path应用一个FIFO,一

个块特殊文件或一个字符特殊文件,则该文件本次打开操作的后续的IO操作设置为非阻塞方式

3.2 open函数和C标准IO库函数的细微区别

  • 以可写的方式fopen一个文件时,如果文件不存在会自动创建,而open一个文件时必须明确指定O_CREAT才会创建文件,否则会出错。
  • 以w或w+方式fopen一个文件时,如果文件存在就会截断为0字节,而open一个文件时必须明确指定O_TRUNC才会截断文件,否则会在原来的数据上改写。
  • 第三个mode参数指定文件权限可以用八进制数表示,如0644;也可以是S_IRUSR,,S_IWUSR等宏定义按位或运算表示。

umask掩码命令 umask
当gcc编译生成一个可执行文件时,创建权限是0777,而最终文件权限是0777 & ~0022 = 0755

[root@menwen-linux ]# umask 0022[root@menwen-linux ]# gcc main.c [root@menwen-linux ]# ll a.out -rwxr-xr-x. 1 root root 5370 1025 21:19 a.out

3.3 关于open函数的一个测试程序

    #include <stdio.h>    #include <unistd.h>    #include <stdlib.h>    #include <sys/types.h>    #include <sys/stat.h>    #include <fcntl.h>    int main(int argc, char *argv[])    {        int fd;        fd = open(argv[1] , O_WRONLY | O_CREAT, 0644);//只写打开文件,若不存在则创建,设置文件权限为0644        if(fd < 0){                perror("open abc fail");                exit(EXIT_FAILURE);        }        printf("fd = %d\n", fd);//打印文件描述符        close(fd);//关闭fd对应的文件        return 0;    }

打印结果如下:

menwen@menwen:~$ gcc open.c menwen@menwen:~$ ./a.out abc fd = 3menwen@menwen:~$ ll abc -rw-r--r-- 1 menwen menwen 0 1025 21:28 abc

由于open函数返回的文件描述符一定是最小未用的描述符值。则创建的abc文件fd=3。

3.4 用程序测出最多打开文件个数

    #include <stdio.h>    #include <stdlib.h>    #include <sys/types.h>    #include <sys/stat.h>    #include <fcntl.h>    int main()    {        int fd;        char name[1024];        int i = 0;        while(1){                sprintf(name, "file%d", ++i);//改变每次打开文件的名字                fd = open(name, O_CREAT, 0777);//打开name文件                if(fd == -1){                        break;                }                printf("%d\n", i);        }        return 0;    }

最后打印的是1021个,加上默认打开的stdin,stdout,stderr。一共1024个。

4. 函数creat

也可以调用一个creat函数创建一个新文件

int creat(const char *pathname, mode_t mode);//返回值,若成功,返回打开文件的文件描述符,若出错,返回-1

此函数等效于:

open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);

由此看出,creat的不足之处是它以写方式打开所创建的文件。

5.函数close

可以调用close函数关闭一个打开文件

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

关闭一个文件时会释放该进程加在该文件上的所有记录锁。

6. 函数read

调用read函数从打开文件中读数据

#include <unistd.h>ssize_t read(int fd, void *buf, size_t count);//返回值:读到的字节数,若已到文件尾,返回0;若出错,返回-1
  • fd是要读的文件的文件描述符;
  • buf使一个通用类型的指针,让读到的数据保存在buf里;
  • count是请求读取的字节数,是一个无符号整型,这就允许一个16位的实现一次读取多达65535个字节;
  • 返回值是ssize_t,一个带符号的整型,以保证能返回正整数字节数,0(表示文件尾端)-1(出错);

7. 函数write

调用write函数向打开的文件写数据

#include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);//返回值:若成功,返回已写的字节数,若出错,返回-1
  • fd是文件描述符
  • buf是要读数据缓冲区的地址
  • count是请求写的字节数,通常与返回值相等,否则表示出错。
  • 返回值是ssize_t,一个带符号的整型,以保证能返回正整数字节数,0(表示文件尾端)-1(出错);

7.1 创建一个具有空洞文件

    #include <stdio.h>    #include <unistd.h>    #include <stdlib.h>    #include <sys/stat.h>    #include <sys/types.h>    #include <fcntl.h>    int main()    {            int fd = open("file.hole", O_WRONLY | O_CREAT, 0777);            if(fd < 0){                    perror("open file.hole err\n");                    exit(EXIT_FAILURE);            }            lseek(fd, 0x1000, SEEK_SET);//从文件头开始写0x1000字节            write(fd, "test", 4);//为空文件必须写一个结尾            close(fd);            return 0;    }

运行结果:

menwen@menwen:~/$ gcc hole.c menwen@menwen:~/$ ./a.out menwen@menwen:~/$ ls -l file.hole -rwxrwxr-x 1 menwen menwen 4100 1026 19:13 file.hole

程序里创建了0x1000(4096D)空洞文件,再加上刚才的”test”4个字节一共是4100字节
使用od命令可以查看文件内容,-c参数是以字符方式打印文件内容。

od -c file.hole0000000  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0*0010000   t   e   s   t0010004

由此可以看出,前面未写入的字节都被填写成”\0”

8. 文件共享

Unix系统支持在不同进程间共享打开文件。内核使用3种数据结构表示打开文件,他们的关系决定了在文件共享方面一个进程对另一个进程的影响。
1. 每个进程在进程表都有一个记录表,记录表中包含一张打开文件描述符表,可将其看成一个矢量,每个描述符占用一项。每个文件描述符相关联的是:
- 文件描述符标志(close_on_exec)
- 指向一个文件表项的指针
2. 内核为所有打开文件维持一张文件表。每个表项包含:
- 文件状态标志(读、写、添加、同步、和非阻塞等)
- 当前文件偏移量
- 指向该文件v结点表项的指针
3. 每个打开文件(或设备)都有一个v结点结构,v结点还包含了文件类型和对文件进行各种操作函数的指针,大多数系统还包含了i结点,i结点包括文件的所有者,文件长度,指向文件实际数据块在磁盘上所载位置的指针。
APUE-打开文件的内核数据结构

9.原子操作

一般而言,原子操作(atomic operation)指的是由多步组成的一个操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

  • 1.追加到一个文件。
    用O_APPEND选项代替“先定位到文件尾端,然后写”的操作。
  • 2.fread和fwrite函数。
    fread函数相当于调用lseek后调用read,fwrite类似。
  • 3.创建一个文件。
    open的O_EXCL和O_CREAT选项代替open和creat函数。

10. dup和dup2函数

复制一个现有的文件描述符

#include <unistd.h>int dup(int oldfd);int dup2(int oldfd, int newfd);//返回值:若成功,返回新的文件描述符,如出错,返回-1
  • dup返回的新文件描述符一定是当前可用文件描述符中的最小值。
  • dup2可以用newfd参数指定新描述符的值。如果newfd已经打开,则先将其关闭,如若newfd等于oldfd则返回newfd而不关闭它。dup2是一个原子操作

11. 函数fcntl

函数fcntl可以改变已经打开文件的属性

#include <unistd.h>#include <fcntl.h>int fcntl(int fd, int cmd, ... /*int arg*/ );//返回值:若成功,则依赖cmd;若出错,返回-1

fcntl函数有5种功能:
1. 复制一个已有的描述符(cmd = F_DUPFD或F_DUPFD_CLOEXEC)
2. 获取/设置文件描述符标志(cmd = F_GETFD或F_SETFD)
3. 获取/设置文件状态标志(cmd = F_GETFL或F_SETFL)
4. 获取/设置异步IO所有权(cmd = F_GETOWN或F_SETOWN)
5. 获取/设置记录锁(cmd = F_GETLK、F_SETLK或F_SETLKW)

12. ioctl函数

#include <sys/ioctl.h>int ioctl(int fd, unsigned long request, ...);//返回值:若出错,返回-1;若成功,返回其他值

ioctl函数用于向设备发控制和配置命令,有些命令也需要读写一些数据,但这些数据是不能用read/write读写的。例如,在串口线手法数据通过read/write操作,而串口的波特率、校验位、等通过ioctl设置。

12.1使用ioctl函数获取终端窗口大小

    #include <stdio.h>    #include <stdlib.h>    #include <unistd.h>    #include <sys/ioctl.h>    int main()    {            struct winsize size;//终端抽象的结构体            if(isatty(STDOUT_FILENO) == 0)//是否是一个终端                    exit(1);            if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0){//获取终端文件的信息写到size中。TIOCGWINSZ是获取终端窗口大小的选项                    perror("ioctl TIOCGWINSZ error");                    exit(1);            }            printf("%d rows, %d columns\n", size.ws_row, size.ws_col);            return 0;    }

运行结果:

menwen@menwen:~/$ ./a.out26 rows, 82 columnsmenwen@menwen:~/$ ./a.out26 rows, 87 columns
0 0
原创粉丝点击