一、Linux中的文件IO

1.1运用编程框架介绍

1.1.1.什么是运用编程

(1)整个嵌入式linux中心课程包含5个点,依照学习顺序顺次是:裸机、C高档、uboot和体系移植、linux运用编程和网络编程、驱动。

(2)典型的嵌入式产品便是基于嵌入式linux操作体系来工作的。典型的嵌入式产品的研发进程便是;第一步让linux体系在硬件上跑起来(体系移植工作),第二步基于linux体系来开发运用程序完结产品功用。

(3)基于linux去做运用编程,其实便是经过调用linux的体系API来完结运用需求完结的使命。

1.1.2.课程思路

(1)经过本课程9个小课程的学习,学会怎么运用linux体系供给的API(和C库函数)来完结必定的功用,经过学习对运用层的编程有所掌握来合作后边驱动的学习。

(2)假如期望深化学习linux运用尤其是网络编程知识,能够去看一些专门介绍这一块的书。

1.1.3.什么是文件IO

IO便是input/output,输入/输出。文件IO的意思便是读写文件。

1.2 文件操作的首要接口API

1.2.1.什么是操作体系API

(1)API是一些函数,这些函数是由linux体系供给支撑的,由运用层程序来调用。

(2)运用层程序经过调用API来调用操作体系中的各种功用,来干活。

(3)学习一个操作体系,其实便是学习运用这个操作体系的API。

(4)今日咱们要运用linux体系来读写文件,手法便是学习linux体系API中和文件IO有关的几个:open、close、write、read、lseek

1.2.2.文件操作的一般步骤

(1)在linux体系中要操作一个文件,一般是先open翻开一个文件,得到一个文件描述符(fd,即file descriptor),然后对文件进行读写操作(或其他操作),终究close封闭文件即可

(2)着重一点:咱们对文件进行操作时,必定要先翻开文件,翻开成功后才能去操作(假如翻开自身失败,后边就不必操作了);终究读写完结之后必定要close封闭文件,不然或许会形成文件损坏。

(3)文件平时是存在块设备中的文件体系中的,咱们把这种文件叫静态文件。当咱们去open翻开一个文件时,linux内核做的操作包含:内核在进程中建立了一个翻开文件的数据结构,记载下咱们翻开的这个文件;内核在内存中请求一段内存,而且将静态文件的内容从块设备中读取到内存中特定地址办理寄存(叫动态文件)。

(4)翻开文件后,今后对这个文件的读写操作,都是针对内存中这一份动态文件的,而并不是针对静态文件的。当咱们对动态文件进行读写后,此时内存中的动态文件和块设备中的静态文件就不同步了,当咱们close封闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件。

(5)常见的一些现象:

第一个:翻开一个大文件时比较慢。由于CPU要将大文件从块设备中读取到内存中去。

第二个:咱们写了一半的文件,假如没有点保存直接关机/断电,重启后文件内容丢掉。由于读写文件的方针文件是寄存在内存中的动态文件。

(6)为什么要这么规划?

由于块设备自身有读写约束(回想NnadFlash、SD等块设备的读写特征),自身对块设备进行操作十分不灵活。而内存能够按字节为单位来操作,而且能够随机操作(内存就叫RAM,random),很灵活。所以内核规划文件操作时就这么规划了。

1.2.3.重要概念:文件描述符

(1)文件描述符其实本质是一个int型数字,这个数字在一个进程中表明一个特定的含义,当咱们open翻开一个文件时,操作体系在内存中构建了一些数据结构来表明这个动态文件,然后回来给运用程序一个数字作为文件描述符,这个数字就和咱们内存中保护这个动态文件的这些数据结构挂钩绑定上了,今后咱们运用程序假如要操作这一个动态文件,只需求用这个文件描述符进行差异。

(2)文件描述符便是用来差异一个程序翻开的多个文件的。

(3)文件描述符的效果域便是当时进程,出了当时进程这个文件描述符就没有含义了

1.3一个简略的文件读写实例

1.3.1.翻开文件与封闭文件

(1)linux中的文件描述符fd的合法范围是0或许一个正整数数,不或许是一个负数。

(2)open回来的fd程序有必要记载好,今后向这个文件的一切操作都要靠这个fd去对应这个文件,终究封闭文件时也需求fd去指定封闭这个文件。假如在咱们封闭文件前fd丢掉了那就惨了,这个文件无法封闭了也无法读写了。

1.3.2.实时查询man手册

(1)当咱们写运用程序时,许多API原型都不或许记住,所以要实时查询,用man手册

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

1.3.3.读取文件内容

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

fd表明要读取哪个文件,fd一般由前面的open回来得到

buf是运用程序自己供给的一段内存缓冲区,用来存储读出的内容

count是咱们要读取的字节数

回来值ssize_t类型是linux内核用typedef重界说的一个类型(其实便是int),回来值表明成功读取的字节数。为什么要这样搞,为的是创立平台无关类型,便利移植。

1.3.4.向文件中写入

(1)写入用write体系调用,write的原型和了解办法和read类似

(2)留意const在buf前面的效果,结合C言语高档专题中的输入型参数和输出型参数一节来了解。

(3)留意buf的指针类型为void,结合C言语高档专题中void类型含义的解说

1.4 open函数的flag详解

1.4.1.读写权限:O_RDWR、O_RDONLY、O_WRONLY

(1)linux中文件有读写权限,咱们在open翻开文件时也能够顺便必定的权限阐明(比如O_RDONLY就表明以只读办法翻开,O_WRONLY表明以只写办法翻开,O_RDWR表明以可读可写办法翻开)

(2)当咱们顺便了权限后,翻开的文件就只能依照这种权限来操作。

1.4.2. 翻开存在并有内容的文件时:O_APPEND、O_TRUNC

(1)思考一个问题:当咱们翻开一个现已存在而且内部有内容的文件再写入时会怎么样?

或许成果1:新内容会替代本来的内容(本来的内容就不见了,丢了)(O_TRUNC)

或许成果2:新内容添加在前面,本来的内容持续在后边

或许成果3:新内容附加在后边,本来的内容还在前面(O_APPEND)

或许成果4:不读不写的时分,本来的文件中的内容坚持不变

(2)O_TRUNC特色去翻开文件时,假如这个文件中本来是有内容的,则本来的内容会被丢弃。这就对应上面的成果1

(3)O_APPEND特色去翻开文件时,假如这个文件中本来是有内容的,则新写入的内容会接续到本来内容的后边,对应成果3

(4)默许不运用O_APPEND和O_TRUNC特色时便是成果4

1.4.3.翻开不存在的文件时:O_CREAT、O_EXCL

(1)思考:当咱们去翻开一个并不存在的文件时会怎样?当咱们open翻开一个文件时假如这个文件名不存在则会翻开文件过错。

(2)vi或许windows下的notepad++,都能够直接翻开一个尚未存在的文件。

(3)open的flag O_CREAT便是为了应对这种翻开一个并不存在的文件的。O_CREAT就表明咱们当时翻开的文件并不存在,咱们是要去创立而且翻开它。

(4)思考:当咱们open运用了O_CREAT,可是文件现已存在的情况下会怎样?经过试验验证发现成果是报错。

(5)结论:open中加入O_CREAT后,不论本来这个文件存在与否都能翻开成功,假如本来这个文件不存在则创立一个空的新文件,假如本来这个文件存在则会重新创立这个文件,本来的内容会被消除掉(有点类似于先删除本来的文件再创立一个新的)

(6)这样或许带来一个问题?咱们本来是想去创立一个新文件的,可是把文件名搞错了弄成了一个老文件名,成果老文件就被意外修正了。咱们期望的效果是:假如我CREAT要创立的是一个现已存在的姓名的文件,则给我报错,不要去创立。

(7)这个效果就要靠O_EXCL标志和O_CREAT标志来结合运用。当这连个标志一起的时分,则没有文件时创立文件,有这个文件时会报错提醒咱们。

(8)open函数在运用O_CREAT标志去创立文件时,能够运用第三个参数mode来指定要创立的文件的权限。mode运用4个数字来指定权限的,其间后边三个很重要,对应咱们要创立的这个文件的权限标志。比如一般创立一个可读可写不行履行的文件就用0666

fd = open(“a.txt”, O_RDWR | O_CREAT |O_EXCL, 0666 );

1.4.4. O_NONBLOCK

(1)堵塞与非堵塞。假如一个函数是堵塞式的,则咱们调用这个函数时当时进程有或许被卡住(堵塞住,本质是这个函数内部要完结的工作条件不具备,当时无法做,要等候条件成熟),函数被堵塞住了就不能立刻回来;假如一个函数是非堵塞式的那么咱们调用这个函数后必定会立即回来,可是函数有没有完结使命不必定。

(2)堵塞和非堵塞是两种不同的规划思路,并没有好坏。总的来说,堵塞式的成果有确保可是时刻没确保;非堵塞式的时刻有确保可是成果没确保。

(3)操作体系供给的API和由API封装而成的库函数,有许多自身便是被规划为堵塞式或许非堵塞式的,所以咱们运用程序调用这些函数的时分心里得十分清楚。

(4)咱们翻开一个文件(设备文件)默许便是堵塞式的,假如你期望以非堵塞的办法翻开文件,则flag中要加O_NONBLOCK标志。

(5)只用于设备文件,而不必于一般文件。

1.4.5. O_SYNC

(1)write堵塞等候底层完结写入才回来到运用层。

(2)无O_SYNC时write仅仅将内容写入底层缓冲区即可回来,然后底层(操作体系中担任完结open、write这些操作的那些代码,也包含OS中读写硬盘等底层硬件的代码)在适宜的时分会将buf中的内容一次性的同步到硬盘中。这种规划是为了提高硬件操作的性能和功率,提高硬件寿命;可是有时分咱们期望硬件不要等候,直接将咱们的内容写入硬盘中,这时分就能够用O_SYNC标志。

1.5 文件读写的一些细节

1.5.1. exit、_exit、_Exit退出进程

(1)当咱们程序在前面步骤操作失败导致后边的操作都没有或许进行下去时,应该在前面的过错监测中完毕整个程序,不应该持续让程序工作下去了。

(2)咱们怎么退出程序?

第一种:在main用return,一般准则是程序正常终止return 0,假如程序反常终止则return -1。

第二种:正式终止进程(程序)应该运用exit或许_exit或许_Exit之一。

1.5.2. errno和perror

(1)errno便是error number,意思便是过错号码。linux体系中对各种常见过错做了个编号,当函数履行过错时,函数会回来一个特定的errno编号来告知咱们这个函数究竟哪里错了。

(2)errno是由OS来保护的一个全局变量,任何OS内部函数都能够经过设置errno来告知上层调用者究竟方才发生了一个什么过错。

(3)errno自身本质是一个int类型的数字,每个数字编号对应一种过错。当咱们只看errno时只能得到一个过错编号数字(比如-37),不适应于人看。

(4)linux体系供给了一个函数perror(意思print error),perror函数内部会读取errno而且将这个不好认的数字直接给转成对应的过错信息字符串,然后print打印出来。

1.5.3. read和write的count

(1)count和回来值的联络。count参数表明咱们想要写或许读的字节数,回来值表明实践完结的要写或许读的字节数。完结的有或许等于想要读写的,也有或许小于(阐明没完结使命)

(2)count再和堵塞非堵塞结合起来,就会更加杂乱。假如一个函数是堵塞式的,则咱们要读取30个,成果暂时只要20个时就会被堵塞住,等候剩余的10个能够读。

(3)有时分咱们写正式程序时,咱们要读取或许写入的是一个很巨大的文件(比如文件有2MB),咱们不或许把count设置为210241024,而应该去把count设置为一个适宜的数字(比如2048、4096),然后经过屡次读取来完结全部读完。

1.5.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再根据硬盘的特性来选择好的机遇去终究写入硬盘中)。

Linux应用编程和网络编程

1.6 linux体系怎么办理文件

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

blog.csdn.net/wwwlyj12332…

(1)文件平时都在寄存在硬盘中的,硬盘中存储的文件以一种固定的形式寄存的,咱们叫静态文件。

(2)一块硬盘中能够分为两大区域:一个是硬盘内容办理表项,另一个是真实存储内容的区域。操作体系拜访硬盘时是先去读取硬盘内容办理表,从中找到咱们要拜访的那个文件的扇区级别的信息,然后再用这个信息去查询真实存储内容的区域,终究得到咱们要的文件。

(3)操作体系开端拿到的信息是文件名,终究得到的是文件内容。第一步便是去查询硬盘内容办理表,这个办理表中以文件为单位记载了各个文件的各种信息,每一个文件有一个信息列表index node(咱们叫inode,i节点,其本质是一个结构体,这个结构体有许多元素,每个元素记载了这个文件的一些信息,其间就包含文件名、文件在硬盘上对应的扇区号、块号那些东西……)

着重:硬盘办理的时分是以文件为单位的,每个文件一个inode,每个inode有一个数字编号,对应一个结构体,结构体中记载了各种信息。

(4)联络平时实践,咱们格局化硬盘(U盘)时发现有:快速格局化和底层格局化。快速格局化十分快,格局化一个32GB的U盘只要1秒钟,一般格局化格局化速度慢。这两个的差异?其实快速格局化便是只删除了U盘中的硬盘内容办理表(其实便是inode),真实存储的内容没有动。这种格局化的内容是有或许被找回的。

如图所示:文件体系先格局化出 inode 和 block 块,假定某文件的权限和特色信息寄存到 inode 4 号方位,这个 inode 记载了实践存储文件数据的 block 号有 4 个,别离为 2、7、13、15,由此,操作体系就能快速地找到文件数据的存储方位。

Linux应用编程和网络编程

Linux应用编程和网络编程

1.6.2.内存中被翻开的文件和vnode(v节点)

(1)一个程序的工作便是一个进程,咱们在程序中翻开的文件就归于某个进程。每个进程都有一个数据结构用来记载这个进程的一切信息(叫进程信息表),表中有一个指针会指向一个文件办理表,文件办理表中记载了当时进程翻开的一切文件及其相关信息。文件办理表顶用来索引各个翻开的文件的index便是文件描述符fd,咱们终究找到的便是一个现已被翻开的文件的办理结构体vnode,virtual node.

(2)一个vnode中就记载了一个被翻开的文件的各种信息,而且咱们只要知道这个文件的fd,就能够很简略的找到这个文件的vnode进而对这个文件进行各种操作。

1.6.3.文件与流的概念

(1)流(stream)对应自然界的水流。文件操作中,文件类似是一个大包裹,里边装了一堆字符,可是文件被读出/写入时都只能一个字符一个字符的进行,而不能一股脑儿的读写,那么一个文件中N多的个字符被挨个一次读出/写入时,这些字符就构成了一个字符流。

(2)流这个概念是动态的,不是静态的。

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

1.7 lseek详解

1.7.1. lseek函数介绍

(1)文件指针:当咱们要对一个文件进行读写时,必定需求先翻开这个文件,所以咱们读写的一切文件都是动态文件。动态文件在内存中的形态便是文件流的形式。

(2)文件流很长,里边有许多个字节。那咱们当时正在操作的是哪个方位?GUI形式下的软件用光标来标识这个当时正在操作的方位,这是给人看的。

(3)在动态文件中,咱们会经过文件指针来表征这个正在操作的方位。所谓文件指针,便是咱们文件办理表这个结构体里边的一个指针。所以文件指针其实是vnode中的一个元素。这个指针表明当时咱们正在操作文件流的哪个方位。这个指针不能被直接拜访,linux体系用lseek函数来拜访这个文件指针。

(4)当咱们翻开一个空文件时,默许情况下文件指针指向文件流的开端。所以这时分去write时写入便是从文件开头开端的。write和read函数自身自带移动文件指针的功用,所以当我write了n个字节后,文件指针会主动向后移动n位。假如需求人为的随意更改文件指针,那就只能经过lseek函数了

(5)read和write函数都是从当时文件指针处开端操作的,所以当咱们用lseek显式的将文件指针移动后,那么再去read/write时便是从移动往后的方位开端的。

(6)回顾前面一节中咱们从空文件,先write写了12字节,然后read时是空的(可是此时咱们翻开文件后发现12字节确实写进来了)。

1.7.2.用lseek核算文件长度

Linux应用编程和网络编程

1.7.3.用lseek构建空洞文件

(1)空洞文件便是这个文件中有一段是空的。

(2)一般文件中间是不能有空的,由于咱们write时文件指针是顺次早年到后去移动的,不或许绕过前面直接到后边。

(3)咱们翻开一个文件后,用lseek往后跳过一段,再write写入一段,就会构成一个空洞文件。

(4)空洞文件办法对多线程一起操作文件是及其有用的。有时分咱们创立一个很大的文件,假如从头开端顺次构建时刻很长。有一种思路便是将文件分为多段,然后多线程来操作每个线程担任其间一段的写入。

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

{

int fd = -1;

char buf[100] = {0};

char writebuf[20] = “abcd”;

int ret = -1;

fd = open(“123.txt”, O_RDWR | O_CREAT);

if (-1 == fd)

{

perror(“文件翻开过错”);

_exit(-1);

}

else

{

printf(“文件翻开成功,fd = %d.\n”, fd);

}

ret = lseek(fd, 10, SEEK_SET);

printf(“lseek, ret = %d.\n”, ret);

#if 1

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

if (-1 == ret)

{

perror(“write失败”);

_exit(-1);

}

else

{

printf(“write成功,写入了%d个字符\n”, ret);

}

#endif

#if 1

lseek(fd, 0, SEEK_SET);

ret = read(fd, buf, 20);

if (-1 == ret)

{

printf(“read失败\n”);

_exit(-1);

}

else

{

printf(“实践读取了%d字节.\n”, ret);

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

}

#endif

close(fd);

_exit(0);

}

1.8 屡次翻开同一文件与O_APPEND

1.8.1.重复翻开同一文件读取

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

(2)成果无非2种情况:一种是fd1和fd2别离读,第二种是接续读。经过试验验证,证明了成果是fd1和fd2别离读。

(3)别离读阐明:咱们运用open两次翻开同一个文件时,fd1和fd2所对应的文件指针是不同的2个独立的指针。文件指针是包含在动态文件的文件办理表中的,所以能够看出linux体系的进程中不同fd对应的是不同的独立的文件办理表。

1.8.2.重复翻开同一文件写入

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

(2)正常情况下咱们有时分需求别离写,有时分又需求接续写,所以这两种自身是没有好坏之分的。要害看用户需求

(3)默许情况下应该是:别离写(试验验证过的)

1.8.3.加O_APPEND处理掩盖问题

有时分咱们期望接续写而不是别离写?办法便是在open时加O_APPEND标志即可

1.8.4. O_APPEND的完结原理和其原子操作性阐明

(1)O_APPEND为什么能够将别离写改为接续写?要害的中心的东西是文件指针。别离写的内部原理便是2个fd具有不同的文件指针,而且互相只考虑自己的位移。可是O_APPEND标志能够让write和read函数内部多做一件工作,便是移动自己的文件指针的一起也去把他人的文件指针一起移动。(也便是说即便加了O_APPEND,fd1和fd2仍是各自具有一个独立的文件指针,可是这两个文件指针相关起来了,一个动了会通知另一个跟着动)

(2)O_APPEND对文件指针的影响,对文件的读写是原子的。

(3)原子操作的含义是:整个操作一旦开端是不会被打断的,有必要直到操作完毕其他代码才能得以调度工作,这就叫原子操作。每种操作体系中都有一些机制来完结原子操作,以确保那些需求原子操作的使命能够工作。

1.9 文件同享的完结办法

1.9.1.什么是文件同享

(1)文件同享便是同一个文件(同一个文件指的是同一个inode,同一个pathname)被多个独立的读写体(简直能够了解为多个文件描述符)去一起(一个翻开尚未封闭的一起另一个去操作)操作。

(2)文件同享的含义有许多:比如咱们能够经过文件同享来完结多线程一起操作同一个大文件,以减少文件读写时刻,提高功率。

Linux应用编程和网络编程

1.9.2.文件同享的3种完结办法

(1)文件同享的中心便是怎么弄出来多个文件描述符指向同一个文件。

(2)常见的有3种文件同享的情况:第一种是同一个进程中屡次运用open翻开同一个文件,第二种是在不同进程中去别离运用open翻开同一个文件(这时分由于两个fd在不同的进程中,所以两个fd的数字能够相同也能够不同),第三种情况是后边要学的,linux体系供给了dup和dup2两个API来让进程仿制文件描述符。

(3)咱们剖析文件同享时的中心关注点在于:别离写/读仍是接续写/读

1.9.3.再论文件描述符

www.2cto.com/kf/201712/7…

【咱们知道在Linux下一切皆文件,因而咱们需求一个东西对这些文件进行办理,此时就需求文件描述符来办理了。文件描述符简称fd,关于内核而言,一切翻开的文件都要经过文件描述符来引证。文件描述符是一个递加的非负整数,一旦当咱们翻开或许创立一个新的文件的时分,内核向进程回来一个文件描述符。

文件描述符一般有以下三个性质:

每个进程都具有自己的一个递加的文件描述符,假如咱们封闭了一个文件描述符所占用的正整数,则这个正整数有或许被其它文件描述符所占用。 单个进程能一起翻开的文件描述符数量遭到limit设置所约束,能够用ulimit -a查看最大文件描述符个数。进程最大翻开文件数目默许是1024个。 根据规则,一切的shell发动新的程序的时分,总是将0、1、2这三个数字的文件描述符翻开为规范输入、规范输出,规范过错】

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

(2)文件描述符这个数字是open体系调用内部由操作体系主动分配的,操作体系分配这个fd时也不是随意分配,也是遵循必定的规律的,咱们现在就要研究这个规律。

(3)操作体系规则,fd从0开端顺次添加。fd也是有最大约束的,在linux的早期版别中(0.11)fd最大是20,所以当时一个进程最多答应翻开20个文件。linux中文件描述符表是个数组(不是链表),所以这个文件描述符表其实便是一个数组,fd是index,文件表指针是value

(4)当咱们去open时,内核会从文件描述符表中选择一个最小的未被运用的数字给咱们回来。也便是说假如之前fd现已占满了0-9,那么咱们下次open得到的必定是10.(可是假如上一个fd得到的是9,下一个不必定是10,这是由于或许前面更小的一个fd现已被close释放掉了)

(5)fd中0、1、2现已默许被体系占用了,因而用户进程得到的最小的fd便是3了。

(6)linux内核占用了0、1、2这三个fd是有用的,当咱们工作一个程序得到一个进程时,内部就默许现已翻开了3个文件,这三个文件对应的fd便是0、1、2。这三个文件别离叫stdin、stdout、stderr。也便是规范输入、规范输出、规范过错。

(7)规范输入一般对应的是键盘(能够了解为:0这个fd对应的是键盘的设备文件),规范输出一般是LCD显示器(能够了解为:1对应LCD的设备文件)

(8)printf函数其实便是默许输出到规范输出stdout上了。stdio中还有一个函数叫fpirntf,这个函数就能够指定输出到哪个文件描述符中。

Linux应用编程和网络编程

1.10 文件描述符的仿制

1.10.1. dup和dup2函数介绍

int dup(int oldfd);

int dup2(int oldfd, int newfd);

dup()和dup2()函数是用来仿制一个文件描述符,能够完结文件同享。由于回来新的文件描述符与本来的文件描述符对应同一个文件表,所以它们同享同一个当时文件的偏移量,因而在利用新的文件描述符向文件中写入数据的时分不会出现数据掩盖的问题。所以它们常常被用来重定向到进程的规范输入、规范输出、规范犯错。

oldfd:被仿制的文件描述符

newfd:在dup2中指定新的文件描述符

1.10.2.运用dup进行文件描述符仿制

(1)dup体系调用对fd进行仿制,会回来一个新的文件描述符(比如本来的fd是3,回来的便是4)

(2)dup体系调用有一个特色,便是自己不能指定仿制后得到的fd的数字是多少,而是由操作体系内部主动分配的,分配的准则恪守fd分配的准则。

(3)dup回来的fd和本来的oldfd都指向oldfd翻开的那个动态文件,操作这两个fd实践操作的都是oldfd翻开的那个文件。实践上构成了文件同享。

(4)dup回来的fd和本来的oldfd一起向一个文件写入时,成果是别离写仍是接续写?验证成果为接续写。

1.10.3.运用dup的缺点剖析

dup并不能指定分配的新的文件描述符的数字,dup2体系调用修复了这个缺点,所以平时项目中实践运用时根据详细情况来决定用dup仍是dup2.

1.10.4.试验:规范输出stdout重定位

(1)之前课程讲过0、1、2这三个fd被规范输入、输出、过错通道占用。而且咱们能够封闭这三个

(2)咱们能够close(1)封闭规范输出,封闭后咱们printf输出到规范输出的内容就看不到了

(3)然后咱们能够运用dup重新分配得到1这个fd,这时分就把oldfd翻开的这个文件和咱们1这个规范输出通道给绑定起来了。这就叫规范输出的重定位。

Linux应用编程和网络编程
(4)能够看出,咱们能够运用close和dup合作进行文件的重定位。

1.10.5.运用dup2进行文件描述符仿制

(1)dup2和dup的效果是相同的,都是仿制一个新的文件描述符。可是dup2答运用户指定新的文件描述符的数字。

(2)运用办法看man手册函数原型即可。

1.10.6. dup2同享文件交叉写入测验

(1)dup2仿制的文件描述符,和本来的文件描述符尽管数字不相同,可是这连个指向同一个翻开的文件

(2)交叉写入的时分,成果是接续写(试验证明的)。

Linux应用编程和网络编程

1.10.7.指令行中重定位指令 >

(1)linux中的shell指令履行后,打印成果都是默许进入stdout的(本质上是由于这些指令比如ls、pwd等都是调用printf进行打印的),所以咱们能够在linux的终端shell中直接看到指令履行的成果。

(2)能否想办法把ls、pwd等指令的输出给重定位到一个文件中(比如2.txt)去,实践上linux终端支撑一个重定位的符号>很简略能够做到这点。

(3)这个>的完结原理,其实便是利用open+close+dup,open翻开一个文件2.txt,然后close封闭stdout,然后dup将1和2.txt文件相关起来即可。

1.11 fcntl函数介绍

1.11.1. fcntl的原型和效果

int fcntl(int fd, int cmd, long arg);

(1)fcntl函数是一个多功用文件办理的东西箱,接纳2个参数+1个变参。第一个参数是fd表明要操作哪个文件,第二个参数是cmd表明要进行哪个指令操作。变参是用来传递参数的,要合作cmd来运用。

(2)cmd的姿态类似于F_XXX,不同的cmd具有不同的功用。学习时没必要去把一切的cmd的含义都弄清楚(也记不住),只需求弄明白一个作为事例,搞清楚它怎么看怎么用就行了,其他的是类似的。其他的当咱们在运用中碰到了一个fcntl的不认识的cmd时再去查man手册即可。

1.11.2. fcntl的常用cmd

F_DUPFD这个cmd的效果是仿制文件描述符(效果类似于dup和dup2),这个指令的功用是从可用的fd数字列表中找一个比arg大或许和arg相同大的数字作为oldfd的一个仿制的fd,和dup2有点像可是不同。dup2回来的便是咱们指定的那个newfd不然就会犯错,可是F_DUPFD指令回来的是>=arg的最小的那一个数字。

实践上调用dup(oldfd);
等效于
fcntl(oldfd, F_DUPFD, 0);

而调用dup2(oldfd, newfd);
等效于
close(oldfd);
fcntl(oldfd, F_DUPFD, newfd);

1.11.3.运用fcntl模仿dup2

Linux应用编程和网络编程

该程序工作成果会是1.txt中aabbaabb…接续写。fd 3和fd 16指向同一份文件表。

1.12 规范IO库介绍

1.12.1.规范IO和文件IO有什么差异

(1)看起来运用时都是函数,可是:规范IO是C库函数,而文件IO是linux体系的API

(2)C言语库函数是由API封装而来的。库函数内部也是经过调用API来完结操作的,可是库函数由于多了一层封装,所以比API要更加好用一些。

(3)库函数比API还有一个优势便是:API在不同的操作体系之间是不能通用的,可是C库函数在不同操作体系中简直是相同的。所以C库函数具有可移植性而API不具有可移植性。

(4)性能上和易用性上看,C库函数一般要好一些。比如IO,文件IO是不带缓存的,而规范IO是带缓存的,因而规范IO比文件IO性能要更高。

1.12.2.常用规范IO函数介绍

常见的规范IO库函数有:fopen、fclose、fwrite、fread、ffulsh(让写入硬盘的数据直接刷入而非在缓冲区等候工作)、fseek

1.12.3.一个简略的规范IO读写文件实例

Linux应用编程和网络编程

二、文件特色

2.1 linux中各种文件类型

2.1.1.一般文件(- regular file)

Linux应用编程和网络编程
(1)文本文件。文件中的内容是由文本构成的,文本指的是ASCII码字符。文件里的内容本质上都是数字(不论什么文件内容本质上都是数字,由于核算机中自身就只要1和0),而文本文件中的数字自身应该被了解为这个数字对应的ASCII码。常见的.c文件, .h文件 .txt文件等都是文本文件。文本文件的优点便是能够被人轻松读懂和编写。所以说文本文件天生便是为人类创造的。

(2)二进制文件。二进制文件中存储的本质上也是数字,只不过这些数字并不是文字的编码数字,而是便是真实的数字。常见的可履行程序文件(gcc编译生成的a.out,arm-linux-gcc编译连接生成的.bin)都是二进制文件。

(3)比照:从本质上来看(便是刨除文件特色和内容的了解)文本文件和二进制文件并没有任何差异。都是一个文件里边寄存了数字。差异是了解办法不同,假如把这些数字就当作数字处理则便是二进制文件,假如把这些数字依照某种编码格局去解码成文本字符,则便是文本文件。

(4)咱们怎么知道一个文件是文本文件仍是二进制文件?在linux体系层面是不差异这两个的(比如之前学过的open、read、write等办法操作文件文件和二进制文件时一点差异都没有),所以咱们无法从文件自身精确知道文件归于哪种,咱们只能本来就知道这个文件的类型然后用这种类型的用法去用他。有时分会用一些后缀名来人为的标记文件的类型。

(5)运用文本文件时,惯例用法便是用文本文件修正器去翻开它、修正它。常见的文本文件修正器如vim、gedit、notepad++、SourceInsight等,咱们用这些文本文件修正器去翻开文件的时分,修正器会read读出文件二进制数字内容,然后依照编码格局去解码将其还原成文字展现给咱们。假如用文本文件修正器去翻开一个二进制文件会怎么?这时分修正器就认为这个二进制文件仍是文本文件然后企图去将其解码成文字,可是解码进程许多数字并不对应有含义的文字所以成了乱码。

(6)反过来用二进制阅览东西去读取文本文件会怎么样?得出的便是文本文字所对应的二进制的编码。

2.1.2.目录文件(d directory)

(1)目录便是文件夹,文件夹在linux中也是一种文件,不过是特别文件。用vi翻开一个文件夹就能看到,文件夹其实也是一种特别文件,里边存的内容包含这个文件的途径,还有文件夹里边的文件列表。

(2)可是文件夹这种文件比较特别,自身并不适合用一般文件的办法来读写。linux中是运用特别的一些API来专门读写文件夹的。

2.1.3.字符设备文件(c character)

2.1.4.块设备文件(b block)

(1)设备文件对应的是硬件设备,也便是说这个文件尽管在文件体系中存在,可是并不是真实存在于硬盘上的一个文件,而是文件体系虚拟制造出来的(叫虚拟文件体系,如/dev /sys /proc等)

(2)虚拟文件体系中的文件大多数不能或许说不必直接读写的,而是用一些特别的API发生或许运用的,详细在驱动阶段会详解。

2.1.5.管道文件(p pipe)

2.1.6.套接字文件(s socket)

2.1.7.符号链接文件(l link)

2.2 常用文件特色获取

2.2.1. stat、fstat、lstat函数简介

(1)每个文件中都顺便了这个文件的一些特色(特色信息是存在于文件自身中的,可是它不像文件的内容相同能够被vi翻开看到,特色信息只能被专用的API翻开看到)

(2)文件特色信息查看的API有三个:stat、fstat、lstat,三个效果相同,参数不同,细节略有不同。

(3)linux指令行下还能够去用stat指令去查看文件特色信息,实践上stat指令内部便是运用stat体系调用来完结的。

(4)stat这个API的效果便是让内核将咱们要查找特色的文件的特色信息结构体的值放入咱们传递给stat函数的buf中,当stat这个API调用从内核回来的时分buf中就被填充了文件的正确的特色信息,然后咱们经过查看buf这种结构体变量的元素就能够得知这个文件的各种特色了。

(5)fstat和stat的差异是:stat是从文件名动身得到文件特色信息结构体,而fstat是从一个现已翻开的文件fd动身得到一个文件的特色信息。所以用的时分假如文件没有翻开(咱们并不想翻开文件操作而仅仅期望得到文件特色)那就用stat,假如文件现已被翻开了然后要特色那就用fstat功率会更高(stat是从磁盘去读取静态文件的,而fstat是从内存读取动态文件的)。

(6)lstat和stat/fstat的不同在于:关于符号链接文件,stat和fstat查阅的是符号链接文件指向的文件的特色,而lstat查阅的是符号链接文件自身的特色。

2.2.2. struct stat结构体简介

struct stat是内核界说的一个结构体,在<sys/stat.h>中声明,所以咱们能够用。这个结构体中的一切元素加起来便是咱们的文件特色信息。

Linux应用编程和网络编程

2.2.3.写个程序来查看一些常见特色信息

Linux应用编程和网络编程

2.3 stat函数的运用事例

2.3.1.用代码判别文件类型

(1)文件类型便是-、d、l …

(2)文件特色中的文件类型标志在struct stat结构体的 元素中,这个元素其实是一个按位来界说的一个位标志(有点类似于ARM CPU的CPSR寄存器的形式位界说)。这个东西有许多个标志位一起构成,记载了许多信息,假如要查找时按位&操作就知道成果了,可是由于这些位界说不简略记住,因而linux体系给咱们事先界说好了许多宏来进行相应操作。

(3)比如S_ISREG宏回来值是1表明这个文件是一个一般文件,假如文件不是一般文件则回来值是0。

2.3.2.用代码判别文件权限设置

(1)st_mode中除了记载了文件类型之外,还记载了一个重要信息:文件权限。

(2)linux并没有给文件权限测验供给宏操作(只给文件类型供给了宏操作),而仅仅供给了位掩码,所以咱们只能用位掩码来自己判别是否具有相应权限。

Linux应用编程和网络编程

2.4 文件权限办理1

2.4.1 st_mode中记载的文件权限位

(1)st_mode本质上是一个32位的数(类型便是unsinged int),这个数里的每一个位表明一个含义。

(2)文件类型和文件的权限都记载在st_mode中。咱们用的时分运用专门的掩码去取出相应的位即可得知相应的信息。

2.4.2. ls -l打印出的权限列表

(1)123456789总共9位,3个一组。第一组三个表明文件的属主(owner、user)对该文件的可读、可写、可履行权限;第2组3个位表明文件的属主地点的组(group)对该文件的权限;第3组3个位表明其他用户(others)对该文件的权限。

(2)属主便是这个文件归于谁,一般来说文件创立时属主便是创立这个文件的那个用户。可是咱们一个文件创立之后还能够用chown指令去修正一个文件的属主,还能够用chgrp指令去修正一个文件地点的组。

2.4.3.文件操作时的权限查看规则

(1)一个程序a.out被履行,a.out中企图去操作一个文件1.txt,这时分怎么断定a.out是否具有对1.txt的某种操作权限呢?

(2)断定办法是:首要1.txt具有9个权限位,规则了3种人(user、group、others)对该文件的操作权限。所以咱们断定1.txt是否能被a.out来操作,要害先搞清楚a.out对1.txt究竟算哪种人。精确的说是看a.out被谁履行,也便是当时程序(进程)是哪个用户的进程。

(3)方才上面说的是我的剖析,究竟对不对还得验证。

2. 5文件权限办理2

2.5.1. access函数查看权限设置

(1)文本权限管控其实蛮杂乱,一般很难很简略的确认对一个文件是否具有某种权限。规划优秀的软件应该是:在操作某个文件之前先判别当时是否有权限做这个操作,假如有再做假如没有则供给过错信息给用户。

(2)access函数能够测验得到当时履行程序的那个用户在当时那个环境下对方针文件是否具有某种操作权限。

Linux应用编程和网络编程
mode: F_OK, R_OK, W_OK, and X_OK

2.5.2. chmod/fchmod与权限修正

(1)chmod是一个linux指令,用来修正文件的各种权限特色。chmod指令只要root用户才有权力去履行修正。

(2)chmod指令其实内部是用linux的一个叫chmod的API完结的。

Linux应用编程和网络编程

2.5.3. chown/fchown/lchown与属主修正

(1)linux中有个chown指令来修正文件属主

(2)chown指令是用chown API完结的

Linux应用编程和网络编程

2.5.4. umask与文件权限掩码

(1)文件掩码是linux体系中保护的一个全局设置,umask的效果是用来设定咱们体系中新创立的文件的默许权限的。

(2)umask指令便是用umask API完结的

2.6 读取目录文件

2.6.1. opendir与readdir函数

man 3

(1) opendir翻开一个目录后得到一个DIR类型的指针给readdir运用

Linux应用编程和网络编程
(2)readdir函数调用一次就会回来一个struct dirent类型的指针,这个指针指向一个结构体变量,这个结构体变量里边记载了一个目录项(所谓目录项便是目录中的一个子文件)。

(3)readdir调用一次只能读出一个目录项,要想读出目录中一切的目录项有必要屡次调用readdir函数。readdir函数内部户记住哪个目录项现已被读过了哪个还没读,所以屡次调用后不会重复回来现已回来过的目录项。当readdir函数回来NULL时就表明目录中一切的目录项现已读完了。

2.6.2.dirent结构体

Linux应用编程和网络编程

2.6.3.读取目录实战演练

Linux应用编程和网络编程

2.6.4.可重入函数介绍

(1)有些函数是可重入的有些是不行重入的,详细概念能够去百度。

(2)readdir函数和咱们前面接触的一些函数是不同的,首要readdir函数直接回来了一个结构体变量指针,由于readdir内部请求了内存而且给咱们回来了地址。屡次调用readdir其实readir内部并不会重复请求内存而是运用第一次调用readdir时分配的那个内存。这个规划办法是readdir不行重入的要害。所以不行重入函数被一个进程修正后会影响一切调用这个函数的进程。

(3)readdir在屡次调用时是有相关的,这个相关也标明readdir函数是不行重入的。

(4)库函数中有一些函数当年刚开端供给时都是不行重入的,后来意识到这种办法不安全,所以重新封装了C库,供给了对应的可重复版别(一般是不行重入版别函数名_r)

三、获取体系信息

3.1 关于时刻的概念

3.1.1. GMT时刻

(1)GMT是格林尼治时刻,也便是格林尼治区域的当地之间。

(2)GMT时刻的含义?用格林尼治的当地时刻作为全球世界时刻,用以描述全球性的事情的时刻,便利咱们记忆。

(3)一般为了便利,一个国家都统一运用一个当地时刻。

3.1.2. UTC时刻

(1)GMT时刻是曾经运用的,近些年来越来越多的运用UTC时刻。

(2)关于北京时刻,能够参阅:www.cnblogs.com/qiuyi21/arc…

UTC + 时区差 = 本地时刻

3.1.3.核算机中与时刻有关的部件

(1)点时刻和段时刻。段时刻=点时刻-点时刻

(2)定时器和实时时钟。定时器(timer)定的时刻便是段时刻,实时时钟(RTC)便是和点时刻有关的一个器件。

3.2 Linux中的时刻

3.2.1. jiffies的引进

(1)jiffies是Linux内核中的一个全局变量,这个变量用来记载以内核的节拍时刻为单位时刻长度的一个数值。

(2)内核装备的时分界说了一个节拍时刻,实践上Linux内核的调度体系工作时便是以这个节拍时刻为时刻片的。

(3)jiffies变量开机时有一个基准值,然后内核每过一个节拍时刻jiffies就会加1,然后到了体系的恣意一个时刻咱们当时时刻就被jiffies这个变量所标注。

3.2.2. Linux体系怎么记载时刻

(1)内核在开机发动的时分会读取RTC硬件获取一个时刻作为初始基准时刻,这个基准时刻对应一个jiffies值(这个基准时刻换算成jiffies值的办法是:用这个时刻减去1970-01-01 00:00:00 +0000(UTC),然后把这个时刻段换算成jiffies数值),这个jiffies值作为咱们开机时的基准jiffies值存在。然后体系工作时每个时钟节拍的结尾都会给jiffies这个全局变量加1,因而操作体系就运用jiffies这个全局变量记载了下来当时的时刻。当咱们需求当时时刻点时,就用jiffies这个时刻点去核算(核算办法便是先把这个jiffies值对应的时刻段算出来,然后加上1970-01-01 00:00:00 +0000(UTC)即可得到这个时刻点)

(2)其实操作体系只在开机时读一次RTC,整个体系工作进程中RTC是无效果的。RTC的真实效果其实是在OS的2次开机之间进行时刻的保存。

(3)了解时必定关键时刻和段时刻结合起来了解。jiffies这个变量记载的其实是段时刻(其实便是当时时刻和1970-01-01 00:00:00 +0000(UTC)这个时刻的差值)

(4)一个时刻节拍的时刻取决于操作体系的装备,现代Linux体系一般是10ms或许1ms。这个时刻其实便是调度时刻,在内核顶用HZ来记载和表明。假如HZ界说成1000那么时钟节拍便是1/HZ,也便是1ms。这些在学习驱动时会用到。

3.2.3. Linux中时刻相关的体系调用

(1)常用的时刻相关的API和C库函数有9个: time/ctime/localtime/gmtime/mktime/asctime/strftime/gettimeofday/settimeofday

(2)time体系调用回来当时时刻以秒为单位的距离1970-01-01 00:00:00 +0000(UTC)曩昔的秒数。这个time内部便是用jiffies换算得到的秒数。其他函数基本都是围绕着time来工作的。

(3)gmtime和localtime会把time得到的秒数变成一个struct tm结构体表明的时刻。差异是gmtime得到的是世界时刻,而localtime得到的是本地(指的是你工作localtime函数的程序地点的核算机所设置的时区对应的本地时刻)时刻。mktime用来完结相反方向的转换(struct tm到time_t)

(4)假如从struct tm动身想得到字符串格局的时刻,能够用asctime或许strftime都能够。(假如从time_t动身想得到字符串格局的时刻用ctime即可)

(5)gettimeofday回来的时刻是由struct timeval和struct timezone这两个结构体来一起表明的,其间timeval表明时刻,而timezone表明时区。settimeofday是用来设置当时的时刻和时区的。

(6)总结:不论用哪个体系调用,终究得到的时刻本质上都是一个时刻(这个时刻终究都是从kernel中记载的jiffies中核算得来的),只不过不同的函数回来的时刻的格局不同,精度不同。