Unix I/O
一个Linux文件就是一个m个字节的序列,所有的I/O设备(例如网络、磁盘和终端)都被模式化为文件,而所有的输入和输出都被当做对此项对应的文件的读和写来进行执行。这种设备优雅的映射为文件的方式,允许Linux内核引出一个简单的、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行:
- 打开文件。应用程序要求内核打开相应的文件,一次访问I/O设备。内核返回一个很小的非负整数,叫做描述符(fd),他在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个标识符。
- shell创建的每个进程都需要打开三个文件:标准输入(fd=0)、标准输出(fd=1)、标准错误(fd=2)。
- 改变当前的文件位置。
- 读写文件。
- 关闭文件。
打开和关闭文件
打开(open)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open (char *filename,int flags,mode_t mode);
返回:若成功则为新文件描述符,若出错为-1。
返回的新文件描述符总是在进程中当前没有打开的最小的描述符。
flags参数指明了进程访问文件的方式:
- O_RDONLY:只读。
- O_WRONLY:只写。
- O_RDWR:可读可写。
- O_CREAT:如果文件不存在,就创建它的一个截断的(空)文件。
- O_TRUNC:如果文件已经存在,就截断它。
-
O_APPEND:在每次写操作前,设置文件位置到文件结尾处。
以上是常用的访问方式,并非全部。
mode参数指定了新文件的访问权限位:
- S_IRUSR:使用者(拥有者)能够读这个文件。
- S_IWUSR:使用者(拥有者)能够写这个文件。
- S_IXUSR:使用者(拥有者)能够执行这个文件。
- S_IRGRP:拥有者所在组的成员能够读这个文件。
- S_IWGRP:拥有者所在组的成员能够写这个文件。
- S_IXGRP:拥有者所在组的成员能够执行这个文件。
- S_IROTH:其他者(任何者)能够读这个文件。
- S_IWOTH:其他者(任何者)能够写这个文件。
- S_IXOTH:其他者(任何者)能够执行这个文件。
关闭
#include <unistd.h>
int close(int fd);
返回:若成功则为0,若出错则为-1.
打开文件使用完后就要用close函数进行关闭,close函数会释放fd指向的文件,并释放该文件所占用的文件描述符。
读和写文件
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t n);
返回:若成功则为度的字节数,若EOF则为0,若出错则为-1。
ssize_t write(int fd,const void *buf,size_t n);
犯规:若成功则为写的字节数,若出错则为-1。
read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。
write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
ssize_t和size_t的区别在于,ssize_t是有符号的长整型,size_t是为无符号的长整型。
定位
lseek函数
详见:
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42031299/article/details/90723673
读取文件元数据
应用程序能够通过调用stat和fstat函数,检索到关于文件的信息(有时也称为文件的元数据)
函数解析详见:
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/codeTZ/article/details/52760382
共享文件
内核用三个相关的数据结构表示打开的文件:
- 描述符表。每个进程都有它独立的描述符表,他的表象是有进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。
- 文件表。打开文件的集合是有一张文件表来表示的,所有的进程共享这张表。每个文件表的表项组成(针对我们的目的)包括当前的文件位置、引用计数(即当前指向该表项的描述符表项数),以及一个指向v-node表中相应表项的指针。
-
v-node表。同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息。
多个描述符可以打开不同的文件,这是一个典型的情况,无共享文件。
多个描述符也可以通过不同文件表项来引用同一个文件。例如,如果以同一个filename调用open函数两次,就会发生这种情况。
父子进程也可以共享文件,假设在fork之前父进程有打开的文件。fork后子进程就有一个父进程描述符表的副本。父子进程共享相同的打开文件表集合,因此共享相同的文件位置。一个更重要的结果就是,在内核删除相应文件表表项之前,父子进程必须都关闭了他们的描述符。
I/O重定向
dup函数
#include <unistd.h>
int dup2(int oldfd,int newfd);
返回:若成功则为非负的描述符,若出错则为-1
dup2函数复制描述符表项oldfd到描述符表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2会复制oldfd之前关闭newfd。
习题
目标文件abcde.txt
一.ffiles1.c
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char c1, c2, c3;
char *fname = argv[1];
fd1 = open(fname, O_RDONLY, 0);
fd2 = open(fname, O_RDONLY, 0);
fd3 = open(fname, O_RDONLY, 0);
dup2(fd2, fd3);
read(fd1, &c1, 1);
read(fd2, &c2, 1);
read(fd3, &c3, 1);
printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);
close(fd1);
close(fd2);
close(fd3);
return 0;
}
结果:
分析:
fd1、fd2、fd3三个文件描述符打开文件abcde.txt共享v-node表,但三个描述符指向的打开文件表不相同,分别为文件、文件2、文件3。
使用dup2函数将fd2复制到fd3,使得两个文件描述符现在都指向文件2,所以两个文件描述符指向的文件位置相同,读了fd1指向的文件1中的c1=a了之后,读fd2和fd3指向的文件2,因为是两个不同的文件,不会相互影响,所以文件2从头开始读,所以c2=a,然后fd2会后移,fd3和fd2位置相同所以c3=b。
二.ffiles2.c
int main(int argc, char *argv[])
{
int fd1;
int s = getpid() & 0x1;
char c1, c2;
char *fname = argv[1];
fd1 = open(fname, O_RDONLY, 0);
read(fd1, &c1, 1);
if (fork()) {
/* Parent */
sleep(s);
read(fd1, &c2, 1);
printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
} else {
/* Child */
sleep(1-s);
read(fd1, &c2, 1);
printf("Child: c1 = %c, c2 = %c\n", c1, c2);
}
return 0;
}
结果:
分析:
前面有讲到父子进程是共享文件位置的,也就不难理解为何子进程c2=b,父进程c2=c了。注(因为sleep函数传参的不同使得父子进程执行顺序有随机性)
三.ffiles3.c
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char *fname = argv[1];
fd1 = open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
write(fd1, "pqrs", 4);
fd3 = open(fname, O_APPEND|O_WRONLY, 0);
write(fd3, "jklmn", 5);
fd2 = dup(fd1); /* Allocates new descriptor */
write(fd2, "wxyz", 4);
write(fd3, "ef", 2);
close(fd1);
close(fd2);
close(fd3);
return 0;
}
结果:
分析:
fd1以O_CREAT|O_TRUNC|O_RDWR创建并打开了一个可读可写的文件,如果文件已存在则将其内容清空;fd3则是可以在文件末尾对文件内容进行添加,以只写的方式打开文件。
起初文件中内容为abcde,但是fd1打开它的时候就清空了然后往文件里面写数据pqrs。
fd3打开文件,在后面加上jklmn,此时文件中内容为pqrsjklmn,fd1的位置上在j处,fd3位置在文件末尾。
用dup函数,让fd2成为fd1的副本,所以fd2的位置也在j处,从j处开始写入数据wxyz,将jklm覆盖,留下一个n,最后再在文件末尾添加ef,得到结果pqrswxyznef。
————————————日志到此结束——————————————