linux往⽂件连续写1mb数据,Linux⽂件操作(⼀)
使⽤⽂件
在这⼀部分当中,我们将会讨论Linux的⽂件以及
⽬录以及如何来管理他们.我们将会学习创建⽂件,打开⽂件,读取⽂件,写⼊⽂件以及关闭⽂件.我们也将会学习程序如何来管理⽬录(例如创建,扫描,删
除).在上⼀部分当中我们使⽤Shell进⾏编程,⽽现在我们要开始使⽤C编程.
在讨论Linux处理⽂件I/O之前,我们将会看⼀些与⽂件,⽬
录以及设备相关的概念.要处理⽂件与⽬录,我们需要使⽤系统调⽤(与Windows
API相类似的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和Linux
Shell中都有⼀个直接到达我们的⽤户主⽬录的命令,也就是~.如果要进⼊其他⽤户的⽬录,我们可以输⼊~user.正如我们所知道的,每⼀个⽤户的
home⽬录都是因为这个特殊的原因⽽创建的⾼层⽬录的⼦⽬录,在这⾥是/home.
在这⾥我们要注意的就是在标准的库函数中并不⽀持⽂件名参数的~符号.
/home
⽬录只是根⽬录/的⼀个⼦⽬录,根⽬录是⽂件系统层次的最⾼层并且在其⼦⽬录中包含所有的系统⽂件.root⽬录通常包含有系统程序的/bin ⽬录,系统
配置⽂件的/etc⽬录,系统库的/lib⽬录.代表物理设备并提供处理这些设备的接⼝的⽂件位于/dev⽬录中.我们可以在Linux File
System Stander上查到更为详细的内容,或者是我们可以使⽤man hier命令来得到更为详细的描述.
⽂件与设备
甚⾄是硬件设备也会表⽰成为⽂件.例如,作为超级⽤户,我们可以将CD-ROM挂载作为⼀个⽂件:
# mount -t iso9660 /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 命令,他将修改⼀个⽂件的修改⽇期,⽽如果这个⽂件不存在则会创建这个⽂件.然⽽他并不会清空他的内容.
write的返回值
$ echo do not want to see this >/dev/null
$ cp /dev/null empty_file
其
他可以在/dev⽬录中到的设备有硬盘,软盘,通信⼝,磁带,CD-ROM,声卡以及⼀些代表系统内部状态的设备.还有⼀个设备/dev/zero,他
可以作为⼀个空字节的源来创建⼀个⽂件⼤⼩为零的⽂件.我们需要超级⽤户的权限来访问其中的⼀些设置.普通的⽤户不可以使⽤程序来直接访问类似于硬盘这样
的低层设备.设备名也许会由系统的不同⽽不同.在Linux发⾏版本中通常会有⼀个需要超级⽤户来运⾏的程序来管理这些设备⽂件,例如mount命令.
设备可以分为字符设备与块设备.区别就在于⼀些设备需要⼀次访问⼀个块.块设备通常是指那些⽀持随机存取的⽂件系统,如硬盘.
系统调⽤与设备驱动
我们可以使⽤⼀些函数来访问和控制设备⽂件.这些函数就是所谓的系统调⽤,是由Unix/Linux直接提供的到操作系统的接⼝.
在
操作系统的核⼼,内核,是⼤量的设备驱动.这是控制系统硬件的低层接⼝的集合.例如会有⼀个磁带驱动,他可以知道如何来启动磁带,向前或是向后,读或是
写.他同时知道磁带每次要写⼊⼀定⼤⼩的数据块.因为磁带是顺序存取的,驱动器不可以直接访问磁带块,⽽是必须要转到的指定的位置.
与此相类似,低层硬盘设备也会每⼀次写⼊⼀个指定的磁盘块,但是可以直接访问所需的磁盘块,因为磁盘是随机存取的设备.
为了提供⼀个相似的接⼝,硬盘驱动器封装了所有依赖于硬件的特征.硬件的材质特征我们可以通过ioctl得到.
/dev中的设备⽂件可以⽤同样的⽅法来使⽤,他们可以打开,读取,写⼊和关闭.例如⽤来打开⼀个常规⽂件的open调⽤可以⽤来访问⼀个⽤户终端,⼀个打印机或是磁带.
⽤来访问设备驱动器的代层系统调⽤包括:
1 open:打开⼀个⽂件或是设备
2 read:从⼀个打开的⽂件或是设备读
3 write:写⼊⼀个⽂件或是设备
4 close:关闭⽂件或是设备
5 ioctl:向设备驱动器传递控制信息
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_t write(int fildes, const void *buf, size_t nbytes);
有了这些知识,我们可以写出我们的第⼀个程序,simple_write.c:
#include #include int main()
{
if ((write(1, “Here is some data\n”, 18)) != 18)
write(2, “A write error has occurred on file descriptor 1\n”,46);
exit(0);
}
这个程序只是简单的在标准输出上打印⼀条信息.当程序结束时,所有打开的描述符都会⾃动关闭,所以我们并不需要显⽰的关闭他们.然⽽当我们处理缓冲区的输出时并不是这样的情况.
运⾏这个程序,我们会得到下⾯的输出结果:
$ simple_write
Here is some data
$
在这⾥有⼀点值得我们注意的就是也许会报告他所写⼊的字节⽐我们所要求的要少.这并不是⼀个必须的错误.在我们的程序中,我们需要松果检查error从⽽检测错误并且调⽤write写⼊其余的数据.
read
read
系统调⽤会从fildes⽂件描述符所指的⽂件中读取nbytes字节的数据并将所读取的数据放在数据区域buf中.他的返回值为实际读取的字节数,这也
许会⽐所要求的数值要⼩.如果⼀个read函数返回0,则并没有读取任何内容,⽽是到达了⽂件的结尾.同样,如果发⽣错误则返回-1.
其语法如下:
#include size_t read(int fildes, void *buf, size_t nbytes);
下⾯的这个简单的程序simple_read.c,将会从标准输⼊读取128个字符到标准输出.如果实际的数据个数⼩于128,则会读取全部的内容.
#include #include int main()
{
char buffer[128];
int nread;
nread = read(0, buffer, 128);
if (nread == -1)
write(2, “A read error has occurred\n”, 26);
if ((write(1,buffer,nread)) != nread)
write(2, “A write error has occurred\n”,27);
exit(0);
}
如果我们运⾏这个程序,我们会得到下⾯的输出结果:
$ echo hello there | simple_read
hello there
$ simple_read <
Files
In this chapter we will be looking at files and directories and how to manipulate
them. We will learn how to create files, o$
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论