Linux文件编程.docx

上传人:b****3 文档编号:3442991 上传时间:2022-11-23 格式:DOCX 页数:23 大小:220.42KB
下载 相关 举报
Linux文件编程.docx_第1页
第1页 / 共23页
Linux文件编程.docx_第2页
第2页 / 共23页
Linux文件编程.docx_第3页
第3页 / 共23页
Linux文件编程.docx_第4页
第4页 / 共23页
Linux文件编程.docx_第5页
第5页 / 共23页
点击查看更多>>
下载资源
资源描述

Linux文件编程.docx

《Linux文件编程.docx》由会员分享,可在线阅读,更多相关《Linux文件编程.docx(23页珍藏版)》请在冰豆网上搜索。

Linux文件编程.docx

Linux文件编程

Linux文件编程

一.Linux文件结构:

在linux中一切皆文件。

与UNIX一样,Linux环境中的文件具有特别重要的意义,因为它们为操作系统的服务和设备提供了一个简单而统一的接口。

这就意味着,通常,程序完全可以像使用文件那样使用磁盘、串行口、打印机和其他设备。

Linux系统把磁盘分成为几个的partition区域,每个partition区域分成若干个Group来方便进行管理。

每个group里面都有inode以及block区域。

每个partition里面的inode以及block分别的都进行编号。

文件系统中实际的使用的都是这些的编号。

一般的,inode区域是存放文件信息的数据区域;block区域才是实际的存放文件实际数据的地方。

Inode中存放对应文件的权限、属性、时间(atime,ctime,mtime)信息等,以及文件的实际存放区域的所有block号码。

目录的inode中存放此目录的相关的权限属性等信息以及其对应的block区域的号码;目录的block数据区域中存放目录下对应的档案以及子目录的文件名以及其各自对应的inode号码。

档案的inode中存放的是档案本身的权限以及属性等的信息,以及档案实际存放数据区域的block的号码;而档案的block数据区域中存放的才是档案的具体的内容数据信息。

1.目录:

Linux是以文件(档案)的形式进行系统管理的。

而档案文件的存放需要inode以及block,档案的存放除了存储其本身包含的内容以外,它还会有一个名字和一些属性,即“管理信息”,例如文件的创建/修改日期和它的访问权限等。

这些属性被保存在文件的inode(结点)数据结构中,它是文件系统中的一个特殊的数据块,它同时还包含文件的长度和文件在磁盘上的存放位置。

系统使用的是文件的inode编号,目录结构仅仅是出于方便人们使用的目的而文件命名。

目录,是用来保存其他文件的结点号和名字也有其专属的inode以及block。

目录文件中的每个数据项都指向某个文件的节点,删除文件名就等以删除对应的链接(ls–idxxx命令用于查看xxx档案/目录,所对应的inode节点号码),你可以通过使用命令ln在不同的目录中创建指向同一个文件的链接。

Linux系统中删除文件只不过是删除的其所在目录的block中的inode节点指向。

而原来此档案所对应的block数据块中的内容并没有被清除。

如果指向某个文件的链接数(即ls-l命令的输出中跟在访问权限后面的那个数字)变为零,就表示该节点以及其指向的数据不再被使用,磁盘上的相应位置就会被即为可用的空间。

文件被安排在目录中,目录中可能还包含子目录。

这些构成了我们所熟悉的文件系统层次结构。

用户(比如:

project)通常会把自己的文件保存在主目录中,可能是目录/root/project,该目录可以再进一步划分为电子邮件、商业信函、工具程序等子目录。

许多UNIX和linux的shell命令都允许通过一个简单的符号,让用户能够进入自己的主目录,这就是波浪线符号~。

要想进入他人的主目录,就键入~usr(~加用户名)即可。

如你所知,每个用户的主目录通常是一个上层目录的子目录,这个上层目录专为此目的而创建,在这个例子中它就是/root目录。

2.文件和设备

硬件设备在Linux操作系统中通常被表示(映射)为文件。

例如:

CD-ROM设备在开机引导时被加载为/dev/hdc,作为超级用户(root),你可以通过如下命令将CD-ROM驱动器挂载为一个文件:

#mount-tiso9660/dev/hdc/mnt/cdrom

#cd/mnt/cdrom

此命令即把CD-ROM挂载为/mnt/cdrom目录下的文件结构。

然后,你就可以像操作普通文件那样在CD-ROM目录中漫游,只不过该目录中的内容是只读(read-only)的。

UNIX和Linux中比较重要的设备文件有三个:

①/dev/console②/dev/tty③/dev/null

(1)/dev/console

这个设备代表的是系统控制台。

错误信息和诊断信息通常会被发送到这个设备。

每个UNIX系统都会有一个指定的终端或显示屏用接收控制台消息。

过去,它可能是一台专用的打印终端。

在现代的工作站和Linux上,它通常是“活跃”的虚拟控制台;而在X窗口系统中,它会使屏幕上一个特殊的控制台窗口。

(2)/dev/tty

如果一个进程有控制终端的话,那么特殊文件/dev/tty就是这个控制终端(键盘和显示屏,或者键盘和窗口)例如:

通过cron运行的进程就没有控制终端,所以它们不能打开/dev/tty

在能够使用该设备文件的情况下,/dev/tty允许程序直接向用户输出信息,而不管用户具体使用的是那种类型的伪终端或硬件终端。

在标准输出被重定向时,这一功能非常有用。

命令ls-R|more就是一个这样的例子,more程序需要提供用户进行键盘操作之后才能显示下一页内容。

注意:

虽然/dev/console设备只有一个,但通过/dev/tty却能访问许多不同的物理设备

(3)/dev/null

这是空(null)设备。

所有写向这个设备的输出都将被丢弃。

而读这个设备会立刻返回一个文件尾标志,所以在cp命令里可以把它用做拷贝空文件的源文件。

人们常把不需要的输出重定向到/dev/null。

创建空文件的另一方法是:

使用touch命令,它的作用是改变文件的修改时间,如果指定名字不存在,就创建一个新文件,但它并不会把有内容的文件变成空文件。

3.系统调用和设备驱动程序

操作系统的核心部分,即内核。

内核也可以理解为是一组设备驱动程序。

这是一些对系统硬件进行控制的底层接口。

例如:

磁带机就有一个与之对应的设备驱动程序,他知道如何启动磁带,如何对它进行前后回绕、如何对它进行读写等。

它还知道磁带必须以固定长度的数据块为单位进行读写。

因为磁带上的数据读写操作完全是线性的,所以该驱动并不能直接访问磁带上的数据块。

而是必须先把它回绕到正确的位置。

类似的,一个底层的硬盘设备驱动程序一次只能读写一整块硬盘扇区,但却能够直接存取硬盘上任意位置上的数据,因为硬盘是一种随机存储设备。

为了向用户提供一个统一的接口,设备驱动程序封装了所有与硬件相关的特性。

硬件的特有功能可通过ioctl完成。

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。

所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。

它的参数个数如下:

intioctl(intfd,intcmd,…);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。

ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。

/dev目录中的设备文件的用法都是一致的,它们都可以被打开、读、写和关闭。

例如:

用来访问普通文件的open调用同样可以用来访问一台用户终端、一台打印机设备或一台磁带机。

用来访问设备驱动程序的底层函数(系统调用)包括:

open

打开文件或设备

read

从打开的文件或设备里读数据

write

向文件或设备写数据

close

关闭文件或设备

ioctl

把控制信息传递给设备驱动程序

系统ioctl用来提供一些与特定硬件设备有关的必要控制(与正常输入输出相反),所以它的用法随设备的不同而不同。

例如:

ioctl调用可以用于回绕磁带机或设置串行口的流控特性。

因此,ioctl并不需要具备可移植性。

此外,每个驱动程序都定义了自己的一组ioctl命令。

4.库函数

在输入输出操作中,直接使用底层系统调用的问题使它们的效率非常低。

为什么呢?

1系统调用会影响系统的性能。

与函数调用相比,系统调用的开销要大些,因为在执行系统调用时,Linux必须从用户代码切换到内核代码运行,然后再返回用户代码。

减少这种开销的一个好方法是,在程序中尽量减少系统调用的次数,并且让每次系统调用完成尽可能多的工作。

例如:

每次读写大量的数据而不是每次仅读写一个字符。

②硬件会对底层系统调用一次所能读写的数据块做出一定的限制。

例如:

磁带机通常的写操作数据块的长度为10k,所以如果卸的数据量不是10k的整数倍,磁带机还是会以10k为单位卷绕磁带,这就在磁带上留下了空隙。

为了给设备和磁盘文件提供更高层的接口,与Unix一样,Linux发行版提供了一系列的标准函数库。

它们是一些由函数构成的集合,你可以把它们包括在自己的程序中去处理呢些与设备和文件有关的问题。

提供输出缓冲功能的标准IO库就是一个这样的例子。

你可以高效地写任意长度的数据块,库函数则在数据满足数据块长度要求时安排执行底层系统调用。

这就极大的降低了系统调用的负面影响。

库函数会有一个与之相对应的标准头文件,例如:

与标准IO库对应的头文件stdio.h

下图显示了Linux系统中各种文件函数与用户、设备驱动程序、内核和硬件之间的关系。

5.底层文件访问

每个运行中的程序被称为进程(process),它有一些与之相关联的文件描述符。

这是一些小值整数,你可以通过它们访问打开的文件或设备。

有多少文件描述符可用取决于系统的配置情况。

当开始运行程序时,它一般会有三个已经打开的文件描述符。

它们是:

0:

标准输入

1:

标准输出

2:

标准出错

二、文件IO

这里提到的文件IO操作指不带缓存的IO操作,也就是说这里所提到的所有的函数的实现都是通过系统调用实现的,而不是直接对IO端口操作实现的。

1.POSIX以及C库调用

文件的操作主要牵涉到了如下五个动作:

打开、关闭、读、写、定位。

在Linux系统中,提供了两套API:

<1>、一套是C标准API:

fopen、fclose、fread、fwrite、fseek;

<2>、一套则是POSIX定义的系统API:

open、close、read、write、seek。

其中POSIX定义的API是系统API,而C标准API是基于系统API的封装,并且提供了额外的缓冲的功能。

因此也可以把它们叫做缓冲I/O函数和非缓冲I/O函数。

为什么要有增加缓冲区这个功能呢?

主要是因为IO操作时,操作系统要从用户态转换为内核态(进程的运行由用户空间转去内核空间),而这个转换过程相对来说比较慢,因此可以通过缓冲的形式减少转换到内核态的次数。

那么,缓冲IO函数又是如何工作的呢?

当用fopen(C库API)打开文件时,除了分配文件句柄(即fd)外,还额外申请了一个缓冲区。

读文件时,会首先读到缓冲区中,然后返回用户需要的部分,多余的部分仍然放在缓冲区,下次再读的时候可以直接从缓冲区中返回;写文件时,会先写到缓冲区中,等缓冲区满后再统一写到文件中。

那么,我们该如何选择哪一组I/O函数呢?

非缓冲I/O函数(即POSIX定义的系统API),每次读写都要进内核,调一个系统调用的函数要比调一个用户空间的函数要慢很多,所以在用户空间开辟I/O缓冲区还是必要的。

但是用缓冲I/O库函数要时刻注意I/O缓冲区和实际文件有可能不一致,在必要时需调用fflush()。

I/O函数也用于读写设备,比如终端或网络设备。

此时通常需要更快的响应,一般不使用缓冲I/O函数。

*PS:

严格来讲,就算是POSIX的I/O函数,仍然是有内核I/O缓冲的,所以write也不一定是直接写到文件的,也可能写到内核I/O缓冲区中,至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有太大差别的,我们不用太关注这一点。

2.阻塞I/O以及非阻塞I/O

文件读写通常有阻塞和非阻塞两种方式,其中阻塞方式是我们比较常见的一种方式,此时函数会阻塞至操作完成。

例如,对于如下一个等待用户输入字符串,并在屏幕上输出的例子:

#include

#include

intmain(void)

{

charbuf[10];

intn=read(STDIN_FILENO,buf,10);

write(STDOUT_FILENO,buf,n);

return0;

}

执行该函数时,read函数会一直阻塞到在屏幕上输入数据并回车(此时STDIN有数据可用)为止。

阻塞IO有一个很大的问题是:

无法实现并发。

当同时进行多个IO操作的时候,前面的文件数据不可用的时候(往往是Socket之类的IPC操作),后面的IO操作无法执行。

非阻塞IO则可以很好的解决这个问题,要使用非阻塞IO操作,需要在open的时候制定O_NONBLOCK标志。

这样,如果设备暂时没有数据可读就返回-1,调用者应该试着再读一次(again)。

这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备:

#include

#include

#include

intmain(void)

{

charbuf[10];//人为主动的定义了一个10字节的缓冲区域

intfd,n;

fd=open("/dev/tty",O_RDONLY|O_NONBLOCK);//以只读&非阻塞I/O方式,系统调用打开文件:

/dev/tty

while

(1)

{

n=read(fd,buf,10);//从打开的文件里面读取内容。

if(n>=0)

break;//if条件满足的时候跳出while

(1)循环。

sleep

(1);//本线程休眠1毫秒

}

write(STDOUT_FILENO,buf,n);

close(fd);

return0;

}

*PS:

为了示例函数简单,我这里没有考虑异常情况(如open失败)的处理,而这些是在实际项目中是必不可少的。

非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者则需要反复查询,这样会一直占着cpu不放。

因此,在使用非阻塞I/O时,通常不会在一个while循环中一直不停地查询(这称为TightLoop),而是每延迟等待一会儿来查询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行。

但是,这样又引入了一个新的问题,可能导致数据读取的不够及时,就拿我前面的例子来说,我在每次循环的时候Sleep了一秒。

如果刚开始Sleep的时候数据可用,但此时却无法立即响应,需要到Sleep结束后钟才能输出结果。

要解圆满解决这个问题,则需要用到select函数,它可以阻塞地同时监视多个设备,还可以设定阻塞等待的超时时间,由于select多见于socket编程场景,这里不大好举例,后续如果会介绍socket编程的时候再详细介绍它。

3、文件描述符fd(FileDiscriptor,fd)

Linux文件的身份证号码

文件描述符是一个非负的整数,当打开一个文件或创建一个文件时,内核向进程返回一个标识该文件的整数,即fd。

注意,若文件不被创建打开(即没有载入进程),是没有文件描述符可言的。

必需明确一个概念:

LINUX中的一切皆是文件。

因此对于特殊的文件有特殊的文件描述符。

特殊文件描述符:

0——标准输入,1——标准输出,2——标准错误输出。

三、Linux系统下的文件编程:

Linux中文件编程可以使用两种方法:

(1)Linux系统调用

(2)C语言库函数

*前者依赖于Linux系统,后者与操作系统是独立的,在任何操作系统下,使用C语言库函数操作文件的方式都是相同的。

=》C库函数调用其实实质上是对系统调用的再次封装。

Linux文件编程方式

常用函数/系统调用

系统调用

open(),read(),write(),lseek()

库函数

fopen(),fread(),fwrite(),flseek()

说到I/O,估计做过硬件的人第一个反应就是芯片管脚的IO口。

我刚开始也一直误解成某一个端口,后来想想这个端口应该是广义上的端口,就是一种接口的概括,而不是特指某一个芯片腿。

在linux中用户通过进程控制来控制相关的系统调用来创建进程、实现进程调度、进程管理等。

这些都是操作系统中的内核服务。

为什么要用相关的系统调用,而不能直接访问内核呢,这是为了更好地保护内核空间所设计的机制。

在linux中为了更好地保护内核空间,将程序的运行空间分为内核空间和用户空间(内核态和用户态)。

分别运行在不同的级别上,逻辑是相互隔离的。

因此用户进程在通常的情况下是不允许访问内核数据,也无法使用内核函数,他们只能在用户空间操作用户数据,掉用户空间的函数。

有些情况下,用户空间的进程需要获得一定的系统服务(调用内核空间程序),这时操作系统就必须利用系统提供给用户的特殊接口,进入到内核空间的具体位置,操作完了之后,在退出来,回到用户空间。

系统调用只有250个左右,按照功能分为进程控制、进程间通信、文件系统控制、系统控制、存储管理、网络管理、socket控制、用户管理等。

系统调用并不是直接与程序员进行交互的,仅是通过一个软中断机制向内核提交请求,以获取内核服务的接口。

这个编程接口是API,也有对应的API函数。

linux中对目录和设备的操作都等同于文件的操作,好家伙,这一下立刻就扩大了文件的定义,这样做的结果是可以大大简化系统对不同设备的处理,提高了效率。

linux中的文件主要分为四种:

普通文件、目录文件、链接文件、设备文件。

怎么突然感觉linux把什么都叫做文件了哦。

内核为了区分这些文件,就有了一个新的概念,文件描述符,就是之前常说的fd。

一个进程启动的时候会打开3个文件:

标准输入、标准输出和标准出错处理。

这3个文件分别对应的文件描述符为0,1,2(也就是宏替换STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO)。

基于文件操作符的IO操作,常常是实现某些IO操作的唯一途径。

不带缓存的IO操作,主要有5个函数:

open、read、write、lseek、close。

线面对个函数进行一个简单的说明。

open函数:

用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。

intopen(constchar*pathname,flags,intperms)

pathname->被打开的文件名

flags:

O_RDONLY->只读方式打开文件;

O_WRONLY->可写方式打开文件;

O_RDWR->读写方式打开文件;

O_CREAT->如果该文件不存在,就创建一个新的文件,并用第三个参数为

其设置权限;

O_EXCL->如果使用O_CREAT时文件存在,则可返回错误消息。

这一参数可

测试文件是否存在;

O_NOCTTY->使用本参数时,如文件为终端,那么终端不可以作为调用

open()系统调用的那个进程的控制终端。

O_TRUNC->如文件已经存在,并且以只读或只写成功打开,那么会先全部

删除文件中原有数据。

O+APPEND->以添加方式打开文件,在打开文件的同时,文件指针指向文

件的末尾。

perm:

被打开文件的存取权限,为8进制表示法。

成功返回文件描述符,失败返回-1

CLOSE函数:

close函数用于关闭一个打开的文件。

当一个进程终止时,他所打开的文件都由内核自动关闭,很多程序都使用这一功能而不显示的关闭文件。

函数原型:

intclose(intfd)

成功返回0失败返回-1

*注意:

open函数返回的文件描述符一定是最小的未用文件描述符,若一个进程在启动时自动打开了0,1,2三个文件描述符,因此该文件运行结果中返回的文件描述符为3。

READ函数:

read函数是用于从指定的文件描述符中读取数据。

当从终端设备中读出数据是通常一次最多读一行。

函数原型:

ssize_tread(intfd,void*buf,size_tcount)

count:

制定最大读出的字节数

成功:

读到的字节数0:

已达到文件尾-1:

出错

WRITE函数:

write函数是用于向打开的文件写数据,写操作从文件的当前位移量处开始。

若磁盘已满或超出该文件的长度,则write函数返回失败。

函数原型:

ssize_twrite(intfd,void*buf,size_tcount)

成功:

已写的字节数-1:

出错

LSEEK函数:

lseek函数是用于在指定的文件描述符中将文件指针定位到相应的位置。

函数原型:

off_tlseek(intfd,off_toffset,intwhence)

offset:

偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可负

whence:

当前位置的基点。

SEEK_SET->当前位置为文件的开头,新位置为偏移量的大小;

SEEK_CUR->当前位置为文件指针的位置,新位置为当前位置加上偏移量

SEEK_END->当前位置为文件的结束,新位置为文件大小加偏移量的大小

成功:

文件当前位置-1:

出错

c语言中所有文件操作函数详解fopen、fwrite、fread、fgetc、fputc、fscanf、fprintf、ftell、fseek等函数

在c中,文件操作都是由库函数来实现的,主要是分为读和写两种操作,以下详细讲解以下所有有关文件操作的邯郸乎的用法:

(1)fopen()函数:

打开文件

包含头文件:

#include

格式:

FILE*fopen(constchar*path,constchar*mode);

参数:

path:

需要打开的文件路径

mode:

文件打开方式

r

以只读方式打开文件,该文件必须存在

r+

以可读写方式打开文件,该文件必须存在

rb+

读写打开一个二进制文件,允许读数据。

rt+

读写打开一个文本文件,允许读和写

w

打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。

若文件不存在则建立该文件。

w+

打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。

若文件不存在则建立该文件。

a

以附加的方式打开只写文件。

若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。

(EOF符保留)

a+

以附加方式打开可读写的文件。

若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。

(原来的EOF符不保留)

wb

只写打开或新建一个二进制文件;只允许写数据。

wb+

读写打开或建立一个二进制文件,允许读和写。

wt+

读写打开或着建立一个文本文件;允许读写。

at+

读写打开一个文本文件,允许读或在文本末追加数据。

ab+

读写打开一个二进制文件,允许读或在文件末追加数据。

现在对上面的文件打开方式做个总结:

文件使用方式由r,w,a,t,b,+六个字符拼成,各字符的含义是:

r(read):

w(write):

a(append):

追加

t(text):

文本文件,可省略不写

b(banary):

二进制文件

+:

读和写

返回值:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 机械仪表

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1