1.典型的嵌入式产品开发的顺序
1、让Linux系统在硬件上跑起来(系统移植工作)
2、基于Linux系统来开发应用程序实现产品功能(应用编程属于这一步骤)
基于Linux做应用编程其实就是调用API来实现应用需要完成的一些任务。
注意:此课程是降低难度版
主要目的:使用linux内核提供的API和c库函数来实现一定的功能
层次:APP+OS 操作系统本身有一部分是驱动,下面是硬件
读写文件的案例中,文件是从硬盘中放着,当打开文件即将其读取到内存,这些硬件需要驱动。操作系统中有文件系统fs,
保证我们用目录+文件名的方式来访问他,给我们提供了一些访问接口,从而用这些“门”去操控硬件和文件。
目录
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.6 lseek函数(E:\Linux\3.AppNet\1.fileio\3.4)
(1)一个进程中两次打开同一个文件,然后分别读取,看结果会怎么样
(1) 一个进程中2个打开同一个文件,得到fd1和fd2.然后看是分别写还是接续写?
六、文件描述符的复制 E:\Linux\3.AppNet\1.fileio\3.6
3.dup2函数实例 E:\Linux\3.AppNet\1.fileio\3.6\file1.c
七、fcntl函数 E:\Linux\3.AppNet\1.fileio\3.7
2.2、第二个参数 int cmd:控制命令选项,用来控制修改什么样的性质,对于cmd的设置选择如下:
一、文件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。
|
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。
|
使用四个数字指定创建文件的权限,与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节点信息 |
1 |
文件信息 |
当前文件长度 |
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;
}