Linux文件操作.docx

上传人:b****0 文档编号:17809422 上传时间:2023-08-04 格式:DOCX 页数:19 大小:27.18KB
下载 相关 举报
Linux文件操作.docx_第1页
第1页 / 共19页
Linux文件操作.docx_第2页
第2页 / 共19页
Linux文件操作.docx_第3页
第3页 / 共19页
Linux文件操作.docx_第4页
第4页 / 共19页
Linux文件操作.docx_第5页
第5页 / 共19页
Linux文件操作.docx_第6页
第6页 / 共19页
Linux文件操作.docx_第7页
第7页 / 共19页
Linux文件操作.docx_第8页
第8页 / 共19页
Linux文件操作.docx_第9页
第9页 / 共19页
Linux文件操作.docx_第10页
第10页 / 共19页
Linux文件操作.docx_第11页
第11页 / 共19页
Linux文件操作.docx_第12页
第12页 / 共19页
Linux文件操作.docx_第13页
第13页 / 共19页
Linux文件操作.docx_第14页
第14页 / 共19页
Linux文件操作.docx_第15页
第15页 / 共19页
Linux文件操作.docx_第16页
第16页 / 共19页
Linux文件操作.docx_第17页
第17页 / 共19页
Linux文件操作.docx_第18页
第18页 / 共19页
Linux文件操作.docx_第19页
第19页 / 共19页
亲,该文档总共19页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

Linux文件操作.docx

《Linux文件操作.docx》由会员分享,可在线阅读,更多相关《Linux文件操作.docx(19页珍藏版)》请在冰点文库上搜索。

Linux文件操作.docx

Linux文件操作

Linux文件操作

(一)

使用文件

在这一部分当中,我们将会讨论Linux的文件以及目录以及如何来管理他们.我们将会学习创建文件,打开文件,读取文件,写入文件以及关闭文件.我们也将会学习程序如何来管理目录(例如创建,扫描,删除).在上一部分当中我们使用Shell进行编程,而现在我们要开始使用C编程.

在讨论Linux处理文件I/O之前,我们将会看一些与文件,目录以及设备相关的概念.要处理文件与目录,我们需要使用系统调用(与WindowsAPI相类似的Unix/Linux调用),但是也存在着一系列的库函数,标准I/O库(stdio),来使得我们的文件处理更为有效.

在这里我们要将讨论处理文件和目录的各种调用,所以我们将会谈到下面的一些内容:

1文件与设备

2系统调用

3库函数

4低层文件访问

5文件管理

6标准I/O库

7格式化输入与输出

8文件与目录维护

9目录扫描

10错误

11/proc文件系统

12高级主题:

fcntl与mmap

Linux文件

也许有人会问:

为什么?

我们要来讨论文件结构吗?

我们已经知道这些了.不错,与Unix中相类似,文件在Linux环境中是相当重要的,因为他们提供了简单并一致的接口来处理系统服务与设备.在Linux中,一切都是文件.这就意味着,在Linux中,程序可以像处理普通文件一样来使用磁盘文件,串口,打印机以及其他的设备.而目录,也是一类特殊的文件.在现代的Unix版本中,包括Linux,甚至是超级用户也不可以对他们直接进行写入操作.所有的普通用户使用高层的opendir/readdir接口来读取目录,而不需要知道目录实现的系统细节.我们稍后将会讨论特殊的目录函数.确实,在Linux下,所有的内容都被看成文件,或者是通过特殊的文件可以访问.即便这样,也存在着一些主要的原则,而这与我们平常所了解和喜爱的文件是不同的.现在我们来看一下我们谈到的特殊情况.

目录

与他的内容一样,一个文件有文件名和一些属性,或者是管理信息;也就是说是这些文件的创建/修改日期与访问权限.这些属性存放在文件的I节点(inode)中,所谓的I节点是文件系统中一个特殊数据块,包含着文件的长度以及在磁盘上的存放位置等信息.系统使用文件的I节点数目,目录结构只是为我们的方便进行文件的命名.

一个目录就是一个包含I节点数目以及其他文件名的文件.每一个目录实体都是指向一个文件I节点的链接,删除了这个文件名,我们也就删除了这个链接(我们可以使用ls-i命令来查看一个文件的I节点数).使用ln命令,我们可以创建在不同的目录中指向同一个文件的链接.如果指向一个文件的链接数(也就是ls-l命令输出中权限后的数字)为零,那么他所指向的I节点和数据就不再使用并标记为空闲.

文件排列在目录,而其中一些也许还会有子目录.这样就形成了我们所熟悉的文件系统结构.一个名为neil用户,通常将他的文件存放在home目录中,也许是/home/neil,而在其中也许会存在一些e-mail等一些子目录.在这里我们要注意就是在Unix和LinuxShell中都有一个直接到达我们的用户主目录的命令,也就是~.如果要进入其他用户的目录,我们可以输入~user.正如我们所知道的,每一个用户的home目录都是因为这个特殊的原因而创建的高层目录的子目录,在这里是/home.

在这里我们要注意的就是在标准的库函数中并不支持文件名参数的~符号.

/home目录只是根目录/的一个子目录,根目录是文件系统层次的最高层并且在其子目录中包含所有的系统文件.root目录通常包含有系统程序的/bin目录,系统配置文件的/etc目录,系统库的/lib目录.代表物理设备并提供处理这些设备的接口的文件位于/dev目录中.我们可以在LinuxFileSystemStander上查找到更为详细的内容,或者是我们可以使用manhier命令来得到更为详细的描述.

文件与设备

甚至是硬件设备也会表示成为文件.例如,作为超级用户,我们可以将CD-ROM挂载作为一个文件:

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

#cd/mnt/cdrom

这样就会启动CD-ROM设备,并将其当将的内容作为/mnt/cdrom文件结构分枝.我们可以像平常的文件一样进入CD-ROM目录,当然,此时这些内容都是只读的.

在Unix和Linux中常见了的三个最重要的设备为/dev/console,/dev/tty和/dev/null.

/dev/console

这个设备代表系统控制台.错误以及论断信息常会发到这个设备.每一个Unix系统都会有一个指定的终端或是屏幕来接收控制台信息.曾经,这是一个复杂的打印终端.在现代的工作站或是Linux中,他通常是一个活动的虚拟控制台,而在X下,则会是在屏幕上的一个特殊的控制窗口.

/dev/tty

/dev/tty是一个进程控制终端的别名,如果有这样的一个进程.例如,由cron运行的进程并不会有一个控制终端,所以他不会打开/dev/tty.

在他可以使用的地方,/dev/tty允许程序将信息直接写给用户,而不需要考虑用户正在使用何种终端.他在标准输出重定向时相当有用.一个例子就是ls-R|more命令,在这里more可以用输出的每一个新页来提示给用户.

在这里我们要注意的是,虽然只有一个/dev/console设备,但是却可以通过/dev/tty来访问多个不同的物理设备.

/dev/null

这是一个空设备.所有写入这个设备的信息都会被丢弃.当读取这个文件时将会立刻到达文件的结尾处,这样他就可以作为使用cp命令的一个空的文件源.所有不希望看到的输出都可以重定向到这个设备.

另一个创建新文件的方法就是使用touch命令,他将修改一个文件的修改日期,而如果这个文件不存在则会创建这个文件.然而他并不会清空他的内容.

$echodonotwanttoseethis>/dev/null

$cp/dev/nullempty_file

其他可以在/dev目录中找到的设备有硬盘,软盘,通信口,磁带,CD-ROM,声卡以及一些代表系统内部状态的设备.还有一个设备/dev/zero,他可以作为一个空字节的源来创建一个文件大小为零的文件.我们需要超级用户的权限来访问其中的一些设置.普通的用户不可以使用程序来直接访问类似于硬盘这样的低层设备.设备名也许会由系统的不同而不同.在Linux发行版本中通常会有一个需要超级用户来运行的程序来管理这些设备文件,例如mount命令.

设备可以分为字符设备与块设备.区别就在于一些设备需要一次访问一个块.块设备通常是指那些支持随机存取的文件系统,如硬盘.

系统调用与设备驱动

我们可以使用一些函数来访问和控制设备文件.这些函数就是所谓的系统调用,是由Unix/Linux直接提供的到操作系统的接口.

在操作系统的核心,内核,是大量的设备驱动.这是控制系统硬件的低层接口的集合.例如会有一个磁带驱动,他可以知道如何来启动磁带,向前或是向后,读或是写.他同时知道磁带每次要写入一定大小的数据块.因为磁带是顺序存取的,驱动器不可以直接访问磁带块,而是必须要转到的指定的位置.

与此相类似,低层硬盘设备也会每一次写入一个指定的磁盘块,但是可以直接访问所需的磁盘块,因为磁盘是随机存取的设备.为了提供一个相似的接口,硬盘驱动器封装了所有依赖于硬件的特征.硬件的材质特征我们可以通过ioctl得到.

/dev中的设备文件可以用同样的方法来使用,他们可以打开,读取,写入和关闭.例如用来打开一个常规文件的open调用可以用来访问一个用户终端,一个打印机或是磁带.

用来访问设备驱动器的代层系统调用包括:

1open:

打开一个文件或是设备

2read:

从一个打开的文件或是设备读

3write:

写入一个文件或是设备

4close:

关闭文件或是设备

5ioctl:

向设备驱动器传递控制信息

ioctl系统调用用来提供必须的硬件控制,所以他会因设备的不同而不同.例如,ioctl调用可以用来重定位磁带或是设置串口的字符流.正是由于这个原因,ioctl从一个机器到另一个机器并不是必须移植的.另外每一个驱动器定义了自己的ioctl命令集.

库函数

直接使用低层的系统来进行输入与输出所存在的问题就是这样的方式并不是十分的有效.为什么呢?

1使用系统调用会有一个不好的结果.与函数调用比较起来系统调用要浪费大量的资源,因为这时Linux要在执行我们的程序代码与执行他的内核代码之间进行切换.一个好的方法是尽量使得在一个程序中所用到的系统最少,同时要使得每一个系统调用做尽可能多的工作,例如,每一次要读出或是写入大量的数据,而不是第一次只读写一个字符.

2由于硬件的限制会在每一次使用低层系统调用来读写的数据尺寸上有许多的限制.例如,对于磁带驱动器来说,他们每一次可以写入的数据块的大小为10k.所以如果我们每一次要写入的数据块大小并不是10k的整倍数,那么磁带就会向前到下一个10k处,这时就会在磁带上留下一段空白.

为了提供一个到设备或是硬盘文件的高层接口,与Unix相类似,Linux发行版本提供了大量的标准库.这是一些我们可以包含在我们的程序中用来处理这些问题的函数的集合.一个很好的例子就是提供了缓冲区输出的标准I/O库.我们可以高效的书写变尺寸的数据块,从而可以使用那些为了提供完整的数据块而安排的低层系统调用.这样就会大大的减少了系统调用的开销.

库函数通常位于手册页的第三部分,而且通常会有一个标准的头文件与之相对,如对于标准输入输出的stdio.h

低层的文件访问

每一个正在运行的程序,被称之为一个进程,他们都有一系列的文件描述符与之相对.这是一些我们可以用来访问打开的文件或是设备的小整数.我们可以使用这些迫切描述符中的多少内容是依赖于我们的系统配置的.当启动一个程序时,他通常会有三个已打开的描述符.他们是:

0:

标准输入

1:

标准输出

2:

标准错误输出

我们可以使用open系统调用来使用其他文件或是设备的描述符,这正是我们稍后将要讨论的问题.然而那些自动打开的文件描述符可以允许我们使用write来创建一些简单的程序.

write

write系统会将buf中的第一个nbytes字节的内容写入与fildes文件描述符相关的文件.他的返回值为实际写入的字节数.如果在文件描述符中发生了错误或是底层的设备驱动器要求块的尺寸时,返回值也许会小于nbytes.如果函数返回0,则说明没有数据写入.如果函数返回-1,则说明在write调用中发生了错误,而这个错误将会存放在errno全局变量中.

write的语法如下:

#include

size_twrite(intfildes,constvoid*buf,size_tnbytes);

有了这些知识,我们可以写出我们的第一个程序,simple_write.c:

#include

#include

intmain()

{

if((write(1,“Hereissomedata\n”,18))!

=18)

write(2,“Awriteerrorhasoccurredonfiledescriptor1\n”,46);

exit(0);

}

这个程序只是简单的在标准输出上打印一条信息.当程序结束时,所有打开的描述符都会自动关闭,所以我们并不需要显示的关闭他们.然而当我们处理缓冲区的输出时并不是这样的情况.

运行这个程序,我们会得到下面的输出结果:

$simple_write

Hereissomedata

$

在这里有一点值得我们注意的就是也许会报告他所写入的字节比我们所要求的要少.这并不是一个必须的错误.在我们的程序中,我们需要松果检查error从而检测错误并且调用write写入其余的数据.

read

read系统调用会从fildes文件描述符所指的文件中读取nbytes字节的数据并将所读取的数据放在数据区域buf中.他的返回值为实际读取的字节数,这也许会比所要求的数值要小.如果一个read函数返回0,则并没有读取任何内容,而是到达了文件的结尾.同样,如果发生错误则返回-1.

其语法如下:

#include

size_tread(intfildes,void*buf,size_tnbytes);

下面的这个简单的程序simple_read.c,将会从标准输入读取128个字符到标准输出.如果实际的数据个数小于128,则会读取全部的内容.

#include

#include

intmain()

{

charbuffer[128];

intnread;

nread=read(0,buffer,128);

if(nread==-1)

write(2,“Areaderrorhasoccurred\n”,26);

if((write(1,buffer,nread))!

=nread)

write(2,“Awriteerrorhasoccurred\n”,27);

exit(0);

}

如果我们运行这个程序,我们会得到下面的输出结果:

$echohellothere|simple_read

hellothere

$simple_read

Files

Inthischapterwewillbelookingatfilesanddirectoriesandhowtomanipulate

them.Wewilllearnhowtocreatefiles,o$

在第一次运行时,我们为我们的程序使用echo命令创建一些输入,这些输入将导入我们的程序.在第二次运行时,我们从一个文件重定向输入.在这种情况下,我们发现文件draft1.txt的第一部分出现在标准输出中.

open

要创建一个新的文件描述符,我们需要使用open系统调用.其语法格式如下:

#include

#include

#include

intopen(constchar*path,intoflags);

intopen(constchar*path,intoflags,mode_tmode);

更为严格的说,我们并不需要包含sys/types.h和sys/stat.h来打开一个POSIX系统,但是在一些Linux系统这却是必须的.

从简单的角度来说,open建立一个到文件或是设备的访问路径.如果调用成功,则会返回一个文件描述符,而这个文件描述符可以用于read,write或是其他的系统调用.这个文件描述符是唯一,而不会与其他正在运行的进程所共享.如果两个程序在同一时间打开了同一个,他们就会分别维护不同的文件描述符.如果他们同时写入文件,他们将会在他们所读入的地方写入文件.这些数据并不会插入,而是一个会覆盖另一个.每一个程序都会记录一个他们所读入或是写入文件的偏移量的信息.我们可以通过使用文件加锁的方法来避免这样的情况发生.

要打开的文件或是设备的名字是以path参数的形式传入的.而oflags参数则是指定了要在打开的文件上进行的动作.

oflags是以命令(mandatory)文件访问或是其他一些可选模式组合的方式来指定的.open调用必须指定下列文件访问模式中的一种:

O_RDONLY以只读方式打开

O_WRONLY以只写方式打开

O_RDWR以读写方式打开

这个调用还可以在oflags参数中包含下列可选模式的组合(使用OR):

O_APPEND在文件末尾写入数据

O_TRUNC将文件的大小设为零,而不管存在的内容

O_CREAT如果需要,以指定的模式创建文件

O_EXCL与O_CREAT配合使用,保证调用创建文件.

open是原子型的,也就是他只作为一个函数调用.工程禁止两个程序同时创建文件.如果文件已经存在,则open失败.

其他一些oflags的可能的参数可以在open手册页中找到,这个手册页可在手册页的第二部分找到(使用man2open).

open如果调用成功则会返回一个新的文件描述符(通常是一个非负整数),如果失败则会返回-1,同时open会设置errno全局变量来指明失败的原因.我们将会在以后的部分中详细的讨论errno.新的文件描述符总是最小的未被使用的文件描述符,这在一些环境下是相当有用的.例如,如果一个程序关闭了他的标准输出,然后调用open函数,文件描述符1可以重新使用,而标准输出也可以高效的重定向到另一个不同的文件或是设备.还有一个被POSIX标准化的creat调用,但是这个函数并不常用.creat并不如我们所希望那样的仅是创建文件,而是会同时打开这个文件,这与使用open函数同时使用O_CREAT|O_WRONLY|O_TRUNCoflags参数的效果是一样的.

初始权限

当我们使用open函数的O_CREAT来创建一个文件时,我们必须使用第三个参数的形式.mode是第三个参数,他是由文件sys/stat中所定义的.这些权限如下:

S_IRUSR:

拥有者读权限

S_IWUSR:

拥有者写权限

S_IXUSR:

拥有者执行权限

S_IRGRP:

组读权限

S_IWGRP:

组写权限

S_IXGRP:

组执行权限

S_IROTH:

其他用户读权限

S_IWOTH:

其他用户写权限

S_IXOTH:

其他用户执行权限

例如下面的例子:

open(“myfile”,O_CREAT,S_IRUSR|S_IXOTH);

这个例子将会生成一个名为myfile的文件,其权限为拥有者的读权限和其他用户的执行权限,而且只有这些权限.

$ls-lsmyfile

0-r------x1neilsoftware0Sep2208:

11myfile*

会有许多的因素影响文件的权限.首先,只有文件在创建时使用的权限.第二,用户的屏蔽位(由umask命令所指定)影响已创建的文件权限.open调用时所指定的模式值以及运行时所保留的用户屏蔽位.例如,如果用户的屏蔽位设置为001并且在创建文件时指定了S_IXOTH权限,则所创建的文件并不会有其他用户的执行权限,因为用户的屏蔽位并没有提供其他用户的执行权限.事实上,open以及creat调用中的标志需要请求设置权限.所请求设置的权限有没有设置则依赖于运行时的umask值.

umask

umask是一个系统变量,当创建一个文件时,可以被用来为一个文件的权限设置屏蔽位.我们可以通过执行umask命令并提供一个新的值从而可以改变这个变量的值.umask的值是一个三位的十六进制数.每一个数字是1,2或4中的数相加的结果值.我们可以从下表中清楚地明白这个意思.每一个不同的数字位可以对应user,group,other的权限.

第一位:

0没有用户的权限被禁止

4用户读权限被禁止

2用户写权限被禁止

1用户执行权限被禁止

第二位:

0没有组权限被禁止

4组读权限被禁止

2组写权限被禁止

1组执行权限被禁止

第三位:

0没有其他用户的权限被禁止

4其他用户读权限被禁止

2其他用户写权限被禁止

1其他用户执行权限被禁止

例如,要禁止group的写与执行权限以及other的写权限,我们可以使用下面的umask值:

DigitValue

10

22

1

32

每一位的值是相加的结果,所以第二位将是2&1,也就是3.所以umask值将为032.

当我们使用open或是creat来创建一个文件时,mode参数将会与umask值相比较.同时在mode参数和umask值中进行了设置的位将会被移除.最终的结果将会是用户可以设置他们的环境来说:

不要创建带有其他用户写权限的文件,仅管创建文件的程序要求这样的权限设置.这样并不会阻止一个程序或是用户在以后使用chmod命令(或是在一个程序中使用chmod系统调用)来增加其他用户的写权限,但是这确实可以避免用户在所有的新文件上检查和设置权限,从而可以起到保护用户的作用.

close

我们使用close系统调用来关闭一个文件描述符,files与其相对应的文件之间的关联.这样这个文件描述符就可以重新被使用.如果调用成功则会返回0,错误返回-1.

其语法格式如下:

#include

intclose(intfildes);

在这里我们要注意就是检查close的返回值是相当重要的.一些文件系统尤其是网络文件系统,当写入文件时并不会报告写入错误,直到文件关闭,从而会造成当执行写入动作并没有真正将数据写入文件.

一个运行的程序同时打开的文件数目是有限制的.这个限制是由limits.h中定义的OPEN_MAX值决定的,会因系统的不同而不同,但是POSIX要求最少为16.这个限制也许会受到本地系统限制的影响.

ioctl

ioctl是一个事物的集合.他提供了一个接口从而可以控制设备的形为以及他们的描述符和服务的配置.终端,文件描述符,套接字,甚至磁带都有为他们所定义的ioctl调用,我们可以查看相关的手册页得到更为详细的内容.POSIX只为流定义了ioctl.其语法格式如下:

#include

intioctl(intfildes,intcmd,...);

ioctl会执行由文件描述符fildes所对应目标上的cmd所指示的函数.他也许会带有第三个可选的参数,这要依赖于具体的设备所提供的函数.

现在我们已经了解了足够多的关于open,read和write系统调用的知识,我们现在可以写一个低层程序,copy_system.c,将一个文件的内容一个字符一个字符地拷贝到另一个文件.

(在整个这个讨论中,我们将使用不同的方法来写这个程序,从而可以比较不同方法之间的效率问题.为简单起见,我们假设输入文件存在,而输出文件不存在,并且所有的读与写操作都是成功的.当然,在真正的程序设计中,我们将会检测这些假设是否真实存在).

#include

#include

#include

#include

intmain()

{

charc;

intin,out;

in=open(“file.in”,O_RDONLY);

out=open(“file.out”,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR);

while(read(in,&c,1)==1)

write(out,&c,1);

exit(0);

}

在这里我们要注意的是,#include必须放在第一行,因为他定义POSIX编译相关的标记,从而会影响其他包含进来的文件.

首先,我们需要创建一个测试输入文件,大小为1Mb,命名为file.in.

如果我们运行这个程序,我们会得到下面的输出:

$TIMEFORMAT=””timecopy_system

4.67user146.90system2:

32.57elapsed99%CPU

...

$ls-lsfile.

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

当前位置:首页 > IT计算机 > 电脑基础知识

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2