Linux 文件(io)
Linux的文件结构
root:该目录为系统管理员(也称作超级管理员)的用户主目录。
bin:bin是Binary的缩写,这个目录存放着最经常使用的命令。
boot:这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件和镜像文件。
deb:deb是Device(设备)的缩写,该目录下存放的是Linux的外部设备,在Linux中访问设备的方式和访问文件的方式是相同的。
etc:所有的配置文件,所有的系统管理所需要配置文件和子目录都存放在这里。
home:用户的主目录,在Linux系统中,每个用户都有一个自己目录,一般该目录名是以用户的账号命名的。
var:存放着不断变化的文件数据,我们习惯将那些经常被修改的目录放在这个目录下。包括各种日志文件。
lib:这个目录里存放着系统最基本的动态链接共享库,其作用类似于Windows里的DLL文件。几乎所有的应用程序都要使用到这个共享库。
usr:系统用户工具和程序
bin:用户命令
sbin:超级用户使用的比较高级的管理程序和系统守护程序
include:标准头文件
lib:库文件
src:内核源代码
tmp:用来存放一些临时文件
media:Linux系统会自动识别一些设备,例如U盘、光驱等,当识别后,Linux会把识别的设备挂载到这个目录下。
mnt:临时挂载其他文件。
proc:包含了进程的相关信息。
1.系统调用
在Linux中,一切皆文件。文件操作在Linux中是非常重要的。为此,Linux内核提供了一组用户进程
与内核进行交互的接口用于对文件和设备进行访问控制,这些接口被称为系统调用。以统一的形式,为应用程序提供了一组文件访问的抽象接口,应用程序不需要关心文件的具体类型,也不用关心其内部实现细节。
常用的系统调用IO函数有5个:open,close,read,write,ioctl,此外还有个非系统调用IO函数lseek,系统调用IO都是不带缓冲的IO。
Linux的文件操作方式
文件描述符(fd)
fd是一个大于等于0的整数。
每打开一个文件,就创建一个文件描述符,通过文件描述符来操作文件。
预定义的文件描述符:
0: 标准输入,对应于已打开的标准输入设备(键盘)
1: 标准输出,对应于已打开的标准输出设备(控制台)
2: 标准错误,对应于已打开的标准错误输出设备(控制台)
(运行程序在proc文件夹中的对应进程文件夹下查看fd文件夹)
多次打开同一个文件,可以得到多个不同的文件描述符。
open函数
open用于创建一个新文件或打开一个已有文件,返回一个非负的文件描述符fd。
0、1、2为系统预定义的文件描述符,分别代表STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。
所以,我们打开的文件描述符fd一般是从3开始的。
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int open(const char *pathname,int flags);
#成功返回文件描述符,失败返回-1
#pathname是打开的文件名
#flags是打开方式选择
flags参数一般为:O_RDONLY(只读打开)、O_WRONLY(只写打开)、O_RDWR(读写打开)三个参数中任选一个。
另外也可以根据需要和以下常值组合使用
1.O_CREAT:若文件不存在,则创建它,此时需要第三个参数mode,最终创建文件的权限 = mode&~umask
补充:系统默认umask是0002,是一个八进制数,这个数字可以通过用户输入命令去修改。
2.O_APPEND:每次写时都增加到文件的尾端
3.O_NONBLOCK:如果pathname对应的是FIFO(命名管道)、块特殊文件或字符特殊文件,则该命令使open操作及后续IO操作设定为非阻塞模式。
4.O_TRUNC:如果此文件存在,以写的方式成功打开,则将文件截断为0.
5.O_NOTTY:如果指定的文件是终端设备,则不把控制终端分配给open函数。
6.O_EXCL:如果同时设置了O_CREAT选项,而文件已经存在,则使用时用此选项会导致函数返回错误,这种做法可以避免多进程并发错误。
示例:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FILE_RW_LEN 1024
int main() {
//第二个参数-文件存在则无法打开
//O_AOOEND —— 追加
int fd = open("./test_open.txt", O_CREAT | O_EXCL | O_RDWR,S_IRWXU | S_IRGRP |S_IXGRP | S_IROTH);
int count = 0;
char buffer[FILE_RW_LEN] = "hello i am test";
if (fd < 0) {
printf("open failed!,reason :%s\n",strerror(errno));
exit(-1);
}
count = write(fd, buffer, strlen(buffer));
printf("written: %d bytes\n",count);
close(fd);
return 0;
}
close函数
close函数用于关闭一个已经打开的文件
#include<unistd.h>
//成功返回0,失败返回-1.
int close(int fd);
示例:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#define FILE1_NAME "file1.txt"
#define FILE2_NAME "file2.txt"
int main()
{
int file1, file2;
char buffer[4096];
int len = 0;
file1 = open(FILE1_NAME, O_RDONLY);
if (file1 < 0)
{
printf("open file failed! reason:%s\n",strerror(errno));
exit(-1);
}
file2 = open(FILE2_NAME, O_CREAT | O_WRONLY ,S_IRUSR | S_IWUSR);
if (file2 < 0)
{
printf("open file failed! reason:%s\n", strerror(errno));
exit(-2);
}
while (len = read(file1, buffer, sizeof(buffer) > 0))
{
write(file2,buffer,len);
}
close(file2);
close(file1);
return 0;
}
用底层文件操作(系统调用)
read
使用IO库函数
fread
read函数
从一个文件描述符中读取count个字节到buff中。
返回值:
- 大于0——实际读取的字节数
- 0——已读到文件尾
- -1——出错
注意:
- 参数3表示最多能接受的字节数,而不是指一定要输入的字节数。
示例:
char buffer[1024];
int cnt = read(fd,buffer,sizeof(buffer));//从标准输入读
read操作从文件的当前偏移量处开始,在成功返回之前,文件偏移量将增加实际读到的字节数。
有几种情况可能导致实际读到的字节数少于要求读的字节数。
注意:读普通文件时,在读到要求字节数之前就到达了文件尾。例如,离文件尾还有30字节,要求读100字节,则read返回30+1,下次调用read是会直接返回0.
write函数
往一个文件描述符里写数据
返回值:
- 成功:返回实际写入的字节数
- 失败:返回-1,设置错误号errno,用strerror(errno)查看
注意:
- 从文件当前指针位置开始写入。文件刚打开时从文件指针指向文件头。
示例:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
char buff[] = "hello world\n";
//1 2 都是输出到控制台
int len = 0;
len = write(1, buff, sizeof(buff));//标准输出
if (len < 0) {
printf("write failed.reason:%s\n",strerror(errno));
}
len = write(2, buff, sizeof(buff));//标准出错输出
if (len < 0)
{
printf("write failed.reason:%s\n", strerror(errno));
}
return 0;
}
注意:
1.write的返回值通常于count相同,否则表示出错。
2.对于普通文件,write操作从文件的当前偏移量开始。
3.若指定了O_APPEND选项,则每次写之前先将文件偏移量设置到文件尾。
4.成功写入只会,文件偏移量增加实际写的字节数。
文件描述符
我们知道在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。在操作这些所谓的文件的时候,我们每操作一次就找一次名字,这会耗费大量的时间和效率。所以Linux中规定每一个文件对应一个索引,这样要操作文件的时候,我们直接找到索引就可以对其进行操作了。
文件描述符(file descriptor)就是内核为了高效管理这些已经被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符来实现。同时还规定系统刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。这意味着如果此时去打开一个新的文件,它的文件描述符会是3,再打开一个文件文件描述符就是4…
Linux内核对所有打开的文件有一个文件描述符表格,里面存储了每个文件描述符作为索引与一个打开文件相对应的关系,简单理解就是下图这样一个数组,文件描述符(索引)就是文件描述符表这个数组的下标,数组的内容就是指向一个个打开的文件的指针。
这只是简单理解,关于文件描述符,Linux给出了3个数据结构
进程级的文件描述符表
系统级的打开文件描述符表
文件系统的i-node表
一个 Linux 进程启动后,会在内核空间中创建一个 PCB 控制块,PCB 内部有一个文件描述符表(File descriptor table),记录着当前进程所有可用的文件描述符,也即当前进程所有打开的文件。进程级的描述符表的每一条记录了单个进程所使用的文件描述符的相关信息,进程之间相互独立,一个进程使用了文件描述符3,另一个进程也可以用3。除了进程级的文件描述符表,系统还需要维护另外两张表:打开文件表、i-node 表。这两张表存储了每个打开文件的打开文件句柄(open file handle)。一个打开文件句柄存储了与一个打开文件相关的全部信息。
系统级的打开文件描述符表
当前文件偏移量(调用read()和write()时更新,或使用lseek()直接修改) 打开文件时的标识(open()的flags参数) 文件访问模式(如调用open()时所设置的只读模式、只写模式或读写模式) 与信号驱动相关的设置 对该文件i-node对象的引用,即i-node 表指针
文件系统的i-node表
文件类型(例如:常规文件、套接字或FIFO)和访问权限 一个指针,指向该文件所持有的锁列表 文件的各种属性,包括文件大小以及与不同类型操作相关的时间戳
- 在进程 A 中,文件描述符 1 和 20 都指向了同一个打开文件表项,标号为 23(指向了打开文件表中下标为 23 的数组元素),这可能是通过调用 dup()、dup2()、fcntl() 或者对同一个文件多次调用了 open() 函数形成的。
- 进程 A 的文件描述符 2 和进程 B 的文件描述符 2 都指向了同一个文件,这可能是在调用 fork() 后出现的(即进程 A、B 是父子进程关系),或者是不同的进程独自去调用 open() 函数打开了同一个文件,此时进程内部的描述符正好分配到与其他进程打开该文件的描述符一样。
- 进程 A 的描述符 0 和进程 B 的描述符 3 分别指向不同的打开文件表项,但这些表项均指向 i-node 表的同一个条目(标号为 1976);换言之,它们指向了同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了 open() 调用。同一个进程两次打开同一个文件,也会发生类似情况。
这就说明:同一个进程的不同文件描述符可以指向同一个文件;不同进程可以拥有相同的文件描述符;不同进程的同一个文件描述符可以指向不同的文件(一般也是这样,除了 0、1、2 这三个特殊的文件);不同进程的不同文件描述符也可以指向同一个文件。
c语言中文件描述符的使用
C语言中可以通过 open 函数返回一个文件的文件描述符,首先创建一个 test.py 文件用于打开,然后创建一个 test.c 文件,输入下面代码保存。 编译后执行,发现新打开文件的文件描述符是3。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
int fd = open("test.py", O_RDONLY);
if (fd == -1) {
return -1;
}
printf("test.py fd = %d \n", fd);
close(fd);
return 0;
}
Linux配置系统最大打开文件描述符个数
(1)系统级限制
理论上系统内存有多少就可以打开多少的文件描述符,但是在实际中内核是会做相应的处理,一般最大打开文件数会是系统内存的10%(以KB来计算),称之为系统级限制。这个数字可以通过 cat /proc/sys/fs/file-max 或者 sysctl -a | grep fs.file-max 命令查看。
更改系统级限制有临时更改和永久更改两种方式:
-
临时更改:session断开或者系统重启后会恢复原来的设置值。使用命令 sysctl -w fs.file-max=xxxx,其中xxxx就是要设置的数字。
-
永久更改:vim编辑 /etc/sysctl.conf 文件,在后面添加 fs.file-max=xxxx,其中xxxx就是要设置的数字。保存退出后还要使用sysctl -p 命令使其生效。
(2)用户级限制
同时为了控制每个进程消耗的文件资源,内核也会对单个进程最大打开文件数做默认限制,即用户级限制。32位系统默认值一般是1024,64位系统默认值一般是65535,可以使用 ulimit -n 命令查看。
更改用户级限制也有临时更改和永久更改两种方式:
-
临时更改:session断开或者系统重启后会恢复原来的设置值。使用命令 ulimit -SHn xxxx 命令来修改,其中xxxx就是要设置的数字。
-
永久更改:vim编辑 /etc/security/limits.conf 文件,修改其中的 hard nofile xxxx 和 soft nofile xxxx,其中xxxx就是要设置的数字。保存后退出。