一.linux应用&网络编程之文件IO

版权声明:本文为博主原创文章,允许转载请注明。谢谢! https://blog.csdn.net/wangweijundeqq/article/details/88548097

有道云笔记地址:http://note.youdao.com/noteshare?id=7db133b182f498d61c2b5883fa460463&sub=0E2B5152A6EC4848AA1D193370ABC025

1.典型的嵌入式产品开发的顺序 

1、让Linux系统在硬件上跑起来(系统移植工作) 

2、基于Linux系统来开发应用程序实现产品功能(应用编程属于这一步骤)

基于Linux做应用编程其实就是调用API来实现应用需要完成的一些任务。 

注意:此课程是降低难度版 

主要目的:使用linux内核提供的API和c库函数来实现一定的功能

层次:APP+OS 操作系统本身有一部分是驱动,下面是硬件 

读写文件的案例中,文件是从硬盘中放着,当打开文件即将其读取到内存,这些硬件需要驱动。操作系统中有文件系统fs, 

保证我们用目录+文件名的方式来访问他,给我们提供了一些访问接口,从而用这些“门”去操控硬件和文件。

目录

一、文件IO概念

二、文件操作的主要接口API

2.1、什么是操作系统API

2.2、linux常用文件IO接口

三、文件操作的一般步骤

3.1在linux下对文件操作的一般步骤

3.2常用文件IO函数详解(使用man命令查询函数具体功能)

(1)、open函数(E:\Linux\3.AppNet\1.fileio\3.2)

(2)、close函数----关闭文件描述符fd指向的动态文件,并存储文件和刷新缓存。

(3)、read函数---Read成功返回读取的字节数,失败返回-1

write函数---Write成功返回写入的字节数,失败返回-1

3.3、打印错误信息

3.4文件IO效率和标准IO

3.5linux系统如何管理文件

3.6 lseek函数(E:\Linux\3.AppNet\1.fileio\3.4)

(1)、 将读写位置移到文件开头时

(2)、将读写位置移到文件尾时

(3)、想要取得目前文件位置时

(4)、用lseek测定文件大小的程序

(5)、用lseek构建空洞文件

四、多次打开同一文件与O_APPEND

4.1重复打开同一文件读取

(1)一个进程中两次打开同一个文件,然后分别读取,看结果会怎么样

4.2重复打开同一文件写入

(1) 一个进程中2个打开同一个文件,得到fd1和fd2.然后看是分别写还是接续写?

(2)加O_APPEND解决覆盖问题

(3)O_APPEND的实现原理和其原子操作性说明

五、文件共享的实现方式 2019/03/03 22:48

1.何谓文件共享

2.、文件共享的3种实现方式

3.剑指文件描述符

六、文件描述符的复制 E:\Linux\3.AppNet\1.fileio\3.6

1.dup函数(文件描述符重定位函数)

1.1、函数原型和头文件      

1.2、函数功能说明

1.3、函数参数

1.4、返回值

2.dup函数实例

3.dup2函数实例 E:\Linux\3.AppNet\1.fileio\3.6\file1.c

4.dup2共享文件交叉写入测试

5.命令行中重定位命令 >

七、fcntl函数 E:\Linux\3.AppNet\1.fileio\3.7

1、函数原型和头文件

2、函数参数

2.1、第一个参数int fd:文件描述符

2.2、第二个参数 int cmd:控制命令选项,用来控制修改什么样的性质,对于cmd的设置选择如下:

2.3、函数返回值

3、函数功能

4、举例:F_DUPFD

八、标准IO库(会使用man查看手册使用即可)

1.、一个简单的标准IO读写文件实例


一、文件IO概念

(1)IO就是input/output,输入/输出。文件IO的意思就是读写文件。

(2) linux文件IO操作有两套大类的操作方式:不带缓存的文件IO操作,带缓存的文件IO操作。不带缓存的属于直接调用系统调用(system call)的方式,高效完成文件输入输出。它以文件标识符(整型)作为文件唯一性的判断依据。这种操作不是ASCI标准的,与系统有关,移植有一定的问题。而带缓存的是在不带缓存的基础之上封装了一层,维护了一个输入输出缓冲区,使之能跨OS,成为ASCI标准,称为标准IO库。不带缓存的方式频繁进行用户态 和内核态的切换,高效但是需要程序员自己维护;带缓冲的方式因为有了缓冲区,不是非常高效,但是易于维护。由此,不带缓冲区的通常用于文件设备的操作,而带缓冲区的通常用于普通文件的操作。

二、文件操作的主要接口API

2.1、什么是操作系统API

(1)API是一些函数,这些函数是由linux系统提供支持的,由应用层程序来使用。

(2)应用层程序通过调用API来调用操作系统中的各种功能,来干活。

(3)学习一个操作系统,其实就是学习使用这个操作系统的API。

(1)今天我们要使用linux系统来读写文件,手段就是学习linux系统API中和文件IO有关的几个。

2.2、linux常用文件IO接口

(1)open、close、write、read、lseek

三、文件操作的一般步骤

3.1在linux下对文件操作的一般步骤

在linux系统中要操作一个文件,一般是先open打开一个文件,得到一个文件描述符,然后对文件进行读写操作(或其他操作),最后close关闭文件即可

实时查man手册

(1)当我们写应用程序时,很多API原型都不可能记得,所以要实时查询,用man手册

(2)man 1 xx查linux shell命令,man 2 xxx查API, man 3 xxx查库函数

例如我们使用到的open、close、write、read函数,在linux下查阅使用方法即可。

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <string.h>



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

{

int fd=-1; //fd file descriptor,文件描述符

char buf[100]={0};

char writebuf[20]="are you ok";

int ret=-1;

//第一步:打开文件

fd=open("a.txt",O_RDWR);

if(-1 == fd)

{

printf("文件打开错误\n");

}

else

{

printf("文件打开成功,fd=%d.\n",fd);

}



//第二步:读写文件

//写文件

ret=write(fd,writebuf,strlen(writebuf));

if(-1==ret)

{

printf(" 写入失败\n");

}

else

{

printf(" 实际写入了%d字节.\n",ret);

printf(" 文件内容是: [%s] .\n",writebuf);

}

//读文件

ret=read(fd,buf,20);

if(-1==ret)

{

printf(" 读取失败\n");

}

else

{

printf(" 实际读取了%d字节.\n",ret);

printf(" 文件内容是: [%s] .\n",buf);

}

//第三步:关闭文件

close(fd);

return 0;

}

3.2常用文件IO函数详解(使用man命令查询函数具体功能)

(1)、open函数(E:\Linux\3.AppNet\1.fileio\3.2)

#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);

Open函数返回打开、创建文件的文件描述符,如果失败返回-1。

Flags:

O_RDONLY   //只读打开

O_WRONLY   //只写打开

O_RDWR    //读、写打开

O_APPEND   //每次写时都追加到文件的尾端

O_CREAT   //若此文件不存在,则创建它。使用时,需要第三个参数mode

O_EXCL   //如果同时指定了O_CREAT,而文件已经存在,则会出错。用此可以测试一个文件是否存在,如果不存在,则创建此文件。

O_TRUNC  //如果此文件存在,而且为只写或读写成功打开,则将其长度截短为0。

O_NONBLOCK  //如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次操作和后续的I/O操作设置非阻塞模式。只用于设备文件,不能用于普通文件。

O_SYNC    //使每次write都等到物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O。

Mode:

使用四个数字指定创建文件的权限,与linux的权限设置相同,如0755

譬如一般创建一个可读可写不可执行的文件就用0666

(2)、close函数----关闭文件描述符fd指向的动态文件,并存储文件和刷新缓存。

#include <unistd.h>

int close(int fd);

(3)、read函数---Read成功返回读取的字节数,失败返回-1

write函数---Write成功返回写入的字节数,失败返回-1

#include <unistd.h>

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

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

count和返回值的关系。count参数表示我们想要写或者读的字节数,返回值表示实际完成的要写或者读的字节数。实现的有可能等于想要读写的,也有可能小于(说明没完成任务)

3.3、打印错误信息

errno就是error number。

如果程序代码中包含 #include <errno.h>,函数调用失败的时候,系统会自动用用错误代码填充errno这个全局变量,取读errno全局变量可以获得失败原因。

函数调用失败是否会设置errno全局变量由函数决定,并不是所有函数调用失败都会设置errno变量。

#include <stdio.h>

void perror(const char *s);

perror ( )用来将上一个函数发生错误的原因输出到标准错误(stderr),参数s 所指的字符串会先打印出,后面再加上错误原因字符串。

3.4文件IO效率和标准IO

(1)文件IO就指的是我们当前在讲的open、close、write、read等API函数构成的一套用来读写文件的体系,这套体系可以很好的完成文件读写,但是效率并不是最高的。

(2)应用层C语言库函数提供了一些用来做文件读写的函数列表,叫标准IO。标准IO由一系列的C库函数构成(fopen、fclose、fwrite、fread),这些标准IO函数其实是由文件IO封装而来的(fopen内部其实调用的还是open,fwrite内部还是通过write来完成文件写入的)。标准IO加了封装之后主要是为了在应用层添加一个缓冲机制,这样我们通过fwrite写入的内容不是直接进入内核中的buf,而是先进入应用层标准IO库自己维护的buf中,然后标准IO库自己根据操作系统单次write的最佳count来选择好的时机来完成write到内核中的buf(内核中的buf再根据硬盘的特性来选择好的实际去最终写入硬盘中)。

3.5linux系统如何管理文件

硬盘中的静态文件和inode(i节点)

(1)文件平时都放在硬盘中,硬盘中以一种固定的形式存放,我们称为静态文件

(2)一块硬盘中分为两大区域:一个是硬盘内容管理表,另一个是真正存储的内容区域。操作系统访问硬盘时先去读取硬盘内容管理表,从中找到我们访问的那个扇区级别的信息,然后用这个信息去查询真正的存储内容的区域,然后得到我们要的文件

(3)管理表中以文件为单位,记录了各个文件的各种信息,每一个文件有一个文件列表(我们叫inode,i节点,其实质是一个结构体,这个结构体有很多元素,每个元素记录了这个文件的一些信息,其中包括文件名,文件在硬盘上对应的扇区号,块号那些东西......

(4)硬盘管理的时候以文件为单位,每个文件一个inode,每个inode有一个数字编号,对应一个结构体,结构体记录了各种信息

内存中被打开的文件和vnode(v节点)

(5)格式化硬盘(U盘)时发现:快速格式化和底层格式化。快速格式化非常快,格式32G只需一秒,普通格式速度慢。两者差异?

快速格式化只是删除了U盘硬盘内容管理表(inode),真正存储的内容没有动,这种格式化可能被找回。

(6)一个程序的运行就是一个进程,我们在程序中打开的文件就属于某个进程。每个进程都有一个数据结构来记录这个进程的所有信息(进程信息),表中有一个指针会指向一个文件管理表,文件管理表中记录了当前进程及其相关信息。

文件管理表中用来索引各个打开的文件的index就是文件描述符f,我们最终找到的就是一个已经被打开的文件的管理结构体vnode

(7)一个vnode中记录了一个被打开的文件的各种信息,而且我们只要知道这个文件的fd,就可以很容易找到这个文件的vnode进而对这个文件进行各操作。

文件和流的概念

(1)文件操作中,文件类似一个包裹,里面装了一堆字符,但是文件被读出/写入时都只能一个字符一个字符的进行,而不能一个文件中N多个字符被挨个一次读出/写入时,这些字符就构成了字符流

(2)流是动态的,不是静态的

(3)编程中提到的流的概念,一般是IO相关的。所以经常叫IO流。文件操作就构成了IO流。

简而言之就是:表表相连 :硬盘内容管理表 、文件管理表

inode 用来表示一个静态文件 

vnode 用来表示一个动态文件

3.6 lseek函数(E:\Linux\3.AppNet\1.fileio\3.4)

lseek()用来控制该文件的读写位置。

lseek就是操作文件指针的

把光标移动的操作在内部就对应着lseek。

打开空文件的时候,默认情况下文件指针指向文件流的开始。所以write写入从头开始,每写一个就自动把文件指针后移,这样就不会让写入的东西被后来的写入覆盖

write和read是隐式的操作文件指针

lseek是显式的操作文件指针

#include <sys/types.h>

#include <unistd.h>

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

lseek(fd, offset, whence(参照物))//fd:哪个文件,从whence开始,往后偏移offset个

其中,whence就三个参数

SEEK_SET 开头

SEEK_CUR 当前

SEEK_END 结尾

(1)、 将读写位置移到文件开头时

lseek(int fd,0,SEEK_SET);//返回0

(2)、将读写位置移到文件尾时

lseek(int fd,0,SEEK_END);//返回文件长度

(3)、想要取得目前文件位置时

lseek(int fd,0,SEEK_CUR);//返回当前文件指针相对于文件开头的偏移量

(4)、用lseek测定文件大小的程序

思路: 1、打开文件,默认文件指针是在开头

2、lseek的返回值是相对于开头偏移的数量

3、lseek到文件末尾,取返回值打印输出

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <string.h>

#include <stdlib.h>

#include <errno.h>



int cal_len(const char *pathname);int cal_len(const char *pathname)//测定文件大小的函数

{

int fd=-1; //fd file descriptor,文件描述符

int ret=-1;

//第一步:打开文件

fd=open(pathname,O_RDONLY);

if(-1 == fd)

{

printf("文件打开错误\n");

perror("open: ");

return -1;

}

//此时文件指针指向文件开头

//我们用lseek将文件指针移动到末尾,然后返回值就是文件指针距离文件开头的偏移量,也就是文件的长度了。

ret = lseek(fd,0,SEEK_END);

return ret;

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

{

int fd=-1; //fd file descriptor,文件描述符

int ret=-1;

if(argc != 2)

{

printf("usage: %s filename\n",argv[0]);

exit(-1);

}

printf("文件长度为:%d字节.\n",cal_len(argv[1]));

return 0;

}

其中:argc 是 段的个数 

argv[0] 代表第一段 

argv[1] 代表第二段

(5)、用lseek构建空洞文件

① char writebuf[20]="abcdwwj";//写入7个字节

② ret = lseek(fd,10,SEEK_SET ); //将读写位置指向文件头后再增加10个位移量

③最终得到的文件大小为7+10=17个字节

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

{

int fd=-1; //fd file descriptor,文件描述符

char buf[100]={0};

char writebuf[20]="abcdwwj";//写入7个字节

int ret=-1;

//第一步:打开文件

fd=open("123.txt",O_RDWR|O_CREAT);//写入一个新的123.txt文件

if(-1 == fd)

{

printf("文件打开错误\n");

perror("open: ");

exit(-1);//正式终止进程(程序)

}

else

{

printf("文件打开成功,fd=%d.\n",fd);

}

ret = lseek(fd,10,SEEK_SET ); //将读写位置指向文件头后再增加10个位移量

printf("文件大小为:%d.\n",ret);



//第二步:读写文件



#if 1

//写文件

ret=write(fd,writebuf,strlen(writebuf));

if(-1==ret)

{

printf(" 写入失败\n");

exit(-1);//正式终止进程(程序)

}

else

{

printf(" 实际写入了%d字节.\n",ret);

printf(" 文件内容是: [%s] .\n",writebuf);

}

#endif

#if 1

//读文件

ret=read(fd,buf,20);

if(-1==ret)

{

printf(" 读取失败\n");

}

else

{

printf(" 实际读取了%d字节.\n",ret);

printf(" 文件内容是: [%s] .\n",buf);

}

#endif

//第三步:关闭文件

close(fd);

return 0;

}

四、多次打开同一文件与O_APPEND

4.1重复打开同一文件读取

(1)一个进程中两次打开同一个文件,然后分别读取,看结果会怎么样

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <string.h>

#include <stdlib.h>

#include <errno.h>



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

{

int fd1=-1,fd2=-1; //fd file descriptor,文件描述符

char buf[100]={0};

char writebuf[20]="are you ok";

int ret=-1;

//第一步:打开文件

fd1=open("a.txt",O_RDWR);

fd2=open("a.txt",O_RDWR);

if((-1 == fd1) || (-1 == fd2))

{

printf("文件打开错误\n");

perror("open: ");

exit(-1);//正式终止进程(程序)

}

else

{

printf("文件打开成功,fd1=%d.fd2=%d.\n",fd1,fd2);

}

//ret = lseek(fd,3,SEEK_SET );

//printf("文件大小为:%d.\n",ret);



//第二步:读写文件



#if 0

//写文件

ret=write(fd,writebuf,strlen(writebuf));

if(-1==ret)

{

printf(" 写入失败\n");

exit(-1);//正式终止进程(程序)

}

else

{

printf(" 实际写入了%d字节.\n",ret);

printf(" 文件内容是: [%s] .\n",writebuf);

}

#endif

#if 1

while(1)

{

//读文件1

memset(buf,0,sizeof(buf));

ret=read(fd1,buf,2);

if(-1==ret)

{

printf(" 读取失败\n");

exit(-1);

}

else

{

//printf(" 实际读取了%d字节.\n",ret);

printf(" fd1文件内容是: [%s] .\n",buf);

}

sleep(1);//休眠一秒钟

//读文件2

memset(buf,0,sizeof(buf));

ret=read(fd2,buf,2);

if(-1==ret)

{

printf(" 读取失败\n");

exit(-1);

}

else

{

//printf(" 实际读取了%d字节.\n",ret);

printf(" fd2文件内容是: [%s] .\n",buf);

}

}

#endif

//第三步:关闭文件

close(fd1);

close(fd2);

return 0;

}

(2)经过实验验证,证明了结果是fd1和fd2分别读。(在a.txt中写入abcdef)

(3)分别读说明:我们使用open两次打开同一个文件时,fd1和fd2所对应的文件指针是不同的2个独立的指针。文件指针是包含在动态文件的文件管理表中的,所以可以看出linux系统的进程中不同fd对应的是不同的独立的文件管理表。

4.2重复打开同一文件写入

(1) 一个进程中2个打开同一个文件,得到fd1和fd2.然后看是分别写还是接续写?

fd1:写are you ok,fd2写ok you are 

int fd1=-1,fd2=-1; //fd file descriptor,文件描述符

char buf[100]={0};

char writebuf[20]="are you ok";

char writebuf1[20]="ok you are";

int ret=-1;

//第一步:打开文件

fd1=open("a.txt",O_RDWR);

fd2=open("a.txt",O_RDWR);

if((-1 == fd1) || (-1 == fd2))

{

printf("文件打开错误\n");

perror("open: ");

exit(-1);//正式终止进程(程序)

}

else

{

printf("文件打开成功,fd1=%d.fd2=%d.\n",fd1,fd2);

}

//ret = lseek(fd,3,SEEK_SET );

//printf("文件大小为:%d.\n",ret);



//第二步:读写文件



#if 1



while(1)

{

//写文件1

ret=write(fd1,writebuf,strlen(writebuf));

if(-1==ret)

{

printf(" 写入失败\n");

exit(-1);//正式终止进程(程序)

}

else

{

printf(" 实际写入了%d字节.\n",ret);

printf(" fd1文件内容是: [%s] .\n",writebuf);

}

sleep(1);//休眠一秒钟

//写文件2

ret=write(fd2,writebuf1,strlen(writebuf1));

if(-1==ret)

{

printf(" 写入失败\n");

exit(-1);//正式终止进程(程序)

}

else

{

printf(" 实际写入了%d字节.\n",ret);

printf(" fd2文件内容是: [%s] .\n",writebuf1);

}

}

#endif



//第三步:关闭文件

close(fd1);

close(fd2);

return 0;

}

fd1的字符串are you ok被覆盖, a.txt中只有ok you are。

结论为分别写(实验验证)

(2)加O_APPEND解决覆盖问题

(1)有时候我们希望接续写而不是分别写?办法就是在open时加O_APPEND标志即可

//第一步:打开文件

fd1=open("a.txt",O_RDWR |O_APPEND);

fd2=open("a.txt",O_RDWR|O_APPEND);

(3)O_APPEND的实现原理和其原子操作性说明

O_APPEND为什么能够将分别写改为接续写?

关键的核心的东西是文件指针。分别写的内部原理就是2个fd拥有不同的文件指针,并且彼此只考虑自己的位移。但是O_APPEND标志可以让write和read函数内部多做一件事情,就是移动自己的文件指针的同时也去把别人的文件指针同时移动。(也就是说即使加了O_APPEND,fd1和fd2还是各自拥有一个独立的文件指针,但是这两个文件指针关联起来了,一个动了会通知另一个跟着动)

五、文件共享的实现方式 2019/03/03 22:48

1.何谓文件共享

文件共享就是同一个文件(同一个文件指的是同一个inode,同一个pathname)被多个独立的读写体(几乎可以理解为多个文件描述符)去同时(一个打开尚未关闭的同时另一个去操作)操作。

文件共享的核心:是多个文件描述符指向同一个文件

2.、文件共享的3种实现方式

下面都是以两个文件描述符(fd1.fd2)为例进行说明

进程表

 文件表

vnode 节点

fd(索引): 文件表指针

文件指针seek位移量

文件信息

0

v节点指针

i节点信息 

文件信息

当前文件长度 

fd1 (文件描述符)

p1指向文件表 1

 

fd2(文件描述符)

p2指向文件表2

 

第一种情况:同一进程内,多次open共享同一文件后的情况

(为什么多次open同一个文件会导致文件内容覆盖的原因)

因为fd1和fd2分别指向了两个不同的文件表,所以都拥有自己的文件位移量。开始各自的文件位移量都为0。但是为什么拥有相同的v节点呢,毕竟它们对应的都是同一个文件file,否则就不能说fd1和fd2结合上了同一个文件file。

比如用fd1写“hello\n”,它的位移量从0增到了6。但是fd2的初始文件位移量也为0,用fd2写时,fd2的初始文件位移量也为0,也从文件的最开始位置写“world\n”,所以”world\n”会覆盖”hello\n”,以后的依次类推,这就是导致覆盖的原因了。

第二种情况:两个进程共享同一个文件

以下两个进程各自都会独立的打开文件file,这是两个不同的进程,分别使用的是自己的文件描述符,然后分别向文件file里面写东西。

每个描述符都指向了各自独立的文件表,但毕竟共享的是同一个文件,所以他们拥有相同的V结点

第三种情况:使用dup实现共享同一文件(只能实现接续写)

从下图中,可以看出,dup后的多个文件描述符,直接共享同一个文件表,所以也就共享同一个文件位移量,且文件表指针相同,故最后都拥有相同的v节点

3.剑指文件描述符

速决高下 挥剑一刹那,入鞘还家..

(1)文件描述符的本质是一个数字,这个数字本质上是进程表中文件描述符表的一个表项,进程通过文件描述符作为index去索引查表得到文件表指针,再间接访问得到这个文件对应的文件表。

(2)文件描述符fd是系统自动分配的,很少见fd为012,是因为0/1/2已经默认被系统占用了。所以open的最小的就是3.

(3)linux内核占用了0、1、2这三个fd是有用的,当我们运行一个程序得到一个进程时,内部就默认已经打开了3个文件,这三个文件对应的fd就是0、1、2。这三个文件分别叫stdin、stdout、stderr。

stdin 标准输入,一般对应的是键盘。//0对应的是键盘的设备文件 

stdout 标准输出,printf的默认输出//1对应的是LCD显示器设备文件 

fprintf:可以指定fd输出到其他文件中 

六、文件描述符的复制 E:\Linux\3.AppNet\1.fileio\3.6

1.dup函数(文件描述符重定位函数)

1.1、函数原型和头文件      

#include <unistd.h>

int dup(int oldfd);

int dup2(int oldfd, int newfd);

1.2、函数功能说明

用来复制一个现存的文件描述符,其实就是让多个文件描述符指向同一个文件(不用多次open实现),比如可以让4、5、6这三个文件描述符指向了同一个文件file.c。

1.2.1、dup:对于复制后的用到的文件描述符,选择的是该进程中0~1023中目前最小并且未用的那个,比如最小的2这个描述符未用,2就会和oldfd一起指向同一个文件,实现文件共享。

1.2.2、dup2:复制后用到的文件描述符数字(newfd)由我们自己指定,如果该描述符已经被用,那么这个描述符将会先被关闭,然后再复制,并且关闭和复制是一个原子操作。

1.3、函数参数

1.3.1、第一个参数int oldfd:需要被复制的文件描述符

1.3.2、第二个参数int newfd:复制后的用到的文件描述符

1.4、返回值

调用成功,返回新复制的文件描述符,失败,返回-1,并且errno被设置

2.dup函数实例

使用dup函数+close函数实现让标准输出printf()的内容不输出到标准输出,而是输出的某个普通文件中。

close(1)关闭标准输出,关闭后我们printf输出到标准输出的内容直接在屏幕上就看不到了


#define FILENAME "1.txt"

int main(void)

{

int fd1=-1,fd2=-1;

fd1=open(FILENAME,O_CREAT|O_RDWR|O_TRUNC, 0644);

if(fd1<0)

{

perror("open");

return -1;

}

printf("fd1=%d.\n",fd1);

close(1);//1就是标准输出stdout,关闭1

/*当1被关闭后,当前最小且未用的文件描述符肯定是1,所以dup会将fd1复制到当前最小未用的1上,这样一来1和fd1都指向了文件1.txt */

//复制文件描述符

fd2=dup(fd1);

printf("fd2 = %d.\n", fd2);

printf("hello world\n");

return -1;

}

从以上答案看出,printf打印的hello world信息,已经输入到了1.txt文件中

3.dup2函数实例 E:\Linux\3.AppNet\1.fileio\3.6\file1.c

dup2和dup的作用是一样的,都是复制一个新的文件描述符。但是dup2允许用户指定新的文件描述符的数字。


#define FILENAME "2.txt"

int main(void)

{

int fd1=-1,fd2=-1;

fd1=open(FILENAME,O_CREAT|O_RDWR|O_TRUNC, 0644);

if(fd1<0)

{

perror("open");

return -1;

}

//复制文件描述符

fd2=dup2(fd1,1);

printf("fd1 = %d.\n", fd1);

printf("fd2 = %d.\n", fd2);

printf("hello world\n");

return -1;



}

dup2函数的第二个参数,用于指定新的描述符,如果这个描述符已经打开了,那就先关闭它,然后再复制,只是这个关闭和复制两个动作是一个原子操作,所以dup2就避免了dup存在的问题。

4.dup2共享文件交叉写入测试

#define FILENAME "2.txt"

int main(void)

{

int fd1=-1,fd2=-1;

fd1=open(FILENAME,O_CREAT|O_RDWR|O_TRUNC, 0644);

if(fd1<0)

{

perror("open");

return -1;

}

//复制文件描述符

fd2=dup2(fd1,1);

printf("fd1 = %d.\n", fd1);

printf("fd2 = %d.\n", fd2);

//printf("hello world\n");



while(1)

{

write(fd1,"aa",2);//文件描述符写入aa

sleep(1);//休眠1s

write(fd2,"bb",2);//文件描述符写入bb

}

close(fd1);

return -1;



}

交叉写入的时候,结果是接续写(实验证明的)

5.命令行中重定位命令 >

Linux重定义命令: > 

ls > 1.txt ,stdout不再输出,此时1.txt被写入ls的结果

这个>的实现原理,其实就是open+close+dup

先使用open打开某个文件,然后利用close将文件描述符1关闭,就是把标准输出关闭,在利用dup将文件描述符1复制出来到某个文件即可)

七、fcntl函数 E:\Linux\3.AppNet\1.fileio\3.7

1、函数原型和头文件

#include <unistd.h>

#include <fcntl.h>

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

2、函数参数

2.1、第一个参数int fd:文件描述符

2.2、第二个参数 int cmd:控制命令选项,用来控制修改什么样的性质,对于cmd的设置选择如下:

a)F_DUPFD:

复制现存的描述符可用来用来模拟dup和dup2,后面会有例子对此用法进行说明。

b)F_GETFD或F_SETFD

获取或设置文件描述符状态这种设置只有一种情况,我们再将exec函数时将为大家举例说明。

c)F_GETFL或F_SETFL:

获取或设置文件状态我们在open时如果没有指定某些状态(如O_NONBLOCK,O_RDWR,O_APPEND等),我们可以利用fcntl 函数补上。所以后续我们在讲阻塞与非阻塞,异步通知,poll机制等时会为大家举例说明。

d)F_GETOWN或F_SETOWN

获取或设置异步io信号接收的所有权我们在讲异步通知或poll机制是将会为大家举例。

e)F_GETLK或F_SETLK或F_SETLKW

:获取或设置记录所属性用我们将高级io的记录锁再为大家举例子

2.3、函数返回值

如果调用成功,那么具体的返回值视cmd的设置而定,具体如下:

a)、F_DUPFD:返回复制后的新的文件描述符

b)、F_GETFD:返回文件描述符状态

c)、F_GETFL:返回文件的文件状态

d)、F_GETOWN:返回文件描述符所有者的进程id

e)、除了F_GETKEASE和F_GETSIG外,其余的设置全部返回0

如果调用失败,不管cmd怎么设置的,一律返回-1,errno被设置

3、函数功能

fcntl是个多功能的工具箱,他 的功能很多。这个API是用来管理文件描述符的

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

操作哪个文件?进行哪个命令操作?…(变参是用来传递参数的 ,配合cmd使用)

cmd的格式是 F_xxx,不 同的cmd有不同用法,不需要记住。

4、举例:F_DUPFD

cmd设置为F_DUPFD时用来模拟dup函数的用法

#define FILENAME "2.txt"

int main(void)

{

int fd1=-1,fd2=-1;

fd1=open(FILENAME,O_CREAT|O_RDWR|O_TRUNC, 0644);

if(fd1<0)

{

perror("open");

return -1;

}

printf("fd1 = %d.\n", fd1);

fd2=fcntl(fd1, F_DUPFD, 6 );

printf("fd2 = %d.\n", fd2);

while(1)

{

write(fd1,"aa",2);

sleep(1);//休眠1s

write(fd2,"bb",2);

}

close(fd1);

return -1;

}

作用:从可用的fd表中找一个大于等于arg的数字,作为oldfd(第一个)的复制——newfd(新的fd),与dup2不同的是,dup2返回的不是指定值那么就是-1,而fcntl用于dup的时候是返回一个大于等于arg的最小的那个数

fcntl还有很多其他功能,比如更改文件的处理时间等等。

八、标准IO库(会使用man查看手册使用即可)

E:\Linux\3.AppNet\1.fileio\3.8

标准io 

C库函数 例如“fopen

文件io

API 例如:open

看起来都是函数,实际上本质是不一样的。 

C库函数 = API + 封装。

1.、一个简单的标准IO读写文件实例

注意到,标准IO返回的不是一个文件描述符,而是一个FILE *,文件,文件指针类型 

他所指向的内容,可以理解为一个打开的文件流。流是一种运动状态而非实质,是一个动态操作的概念。

#define FILENAME "1.txt"

int main(void)

{

FILE *fp = NULL;

size_t len = -1;

//int array[10] = {1, 2, 3, 4, 5};

char buf[100] = {0};

fp = fopen(FILENAME, "r+");

if (NULL == fp)

{

perror("fopen");

exit(-1);

}

printf("fopen success. fp = %p.\n", fp);

// 在这里去读写文件

//记得读之前最好memset(buf , 0 ,sizeof(buf))清空buffer 

memset(buf, 0, sizeof(buf));

len = fread(buf, 1, 10, fp);

printf("len = %d.\n", len);

printf("buf is: [%s].\n", buf);



#if 0

fp = fopen(FILENAME, "w+");

if (NULL == fp)

{

perror("fopen");

exit(-1);

}

printf("fopen success. fp = %p.\n", fp);

// 在这里去读写文件

//len = fwrite("abcde", 1, 5, fp);

//len = fwrite(array, sizeof(int), sizeof(array)/sizeof(array[0]), fp);

len = fwrite(array, 4, 10, fp);

printf("len = %d.\n", len);

#endif

fclose(fp);

return 0;

}

猜你喜欢

转载自blog.csdn.net/wangweijundeqq/article/details/88548097
今日推荐