一.Linux文件结构
与UNIX一样,Linux环境中的文件具有特别重要的意义,因为它们的操作系统服务和设备提供了一个简单而统一的接口。在linux中一切皆文件。
这就意味着,通常程序完全可以像使用文件那样使用磁盘文件、串行口、打印机和其他设备。
1.目录
文件,除了本身包含的内容以外,它还会有一个名字和一些属性,即“管理信息”,例如文件的创建/修改 日期和它的访问权限等。这些属性被保存在文件的inode(结点)数据结构中,它是文件系统中的一个特殊的数据块,它同时还包含文件的长度和文件在磁盘上的存放位置。系统使用的是文件的inode编号,目录结构仅仅是出于方便人们使用的目的而文件命名。
目录是文件,它用来保存其他文件的结点号和名字。目录文件中的每个数据项都指向某个文件的节点,删除文件名就等以删除对应的链接(文件的节点号可以可以通过ln -i 命令查看)你可以通过使用命令ln 在不同的目录中创建指向同一个文件的链接。如果指向某个文件的链接数(即ls -l 命令的输出中跟在访问权限后面的那个数字)变为零,就表示该节点以及其指向的数据不再被使用,磁盘上的相应位置就会被即为可用的空间。
文件被安排在目录中,目录中可能还包含子目录。这些构成了我们所熟悉的文件系统层次结构。用户(比如:project)通常会把自己的文件保存在主目录中,可能是目录/root/project,该目录可以再进一步划分为电子邮件、商业信函、工具程序等子目录。许多UNIX和linux的shell命令都允许通过一个简单的符号,让用户能够进入自己的主目录,这就是波浪线符号~。要想进入他人的主目录,就键入~usr(~加用户名)即可。如你所知,每个用户的主目录通常是一个上层目录的子目录,这个上层目录专为此目的而创建,在这个例子中它就是/root目录。
注意:不幸的是,标准库函数不能识别文件名参数中的波浪线符号 |
/root 目录本身又是根目录/的子目录,根目录位于目录层次的最顶端,它在它的各级子目录中包含着系统的所以文件。根目录中通常包含用于存放系统程序(二进制可执行文件)的/bin子目录、用于存放系统配置文件的/etc子目录和用于存放系统函数库的/lib子目录。代表物理设备并为这些设备提供接口文件按照惯例会被放在/dev子目录中。
2.文件和设备
硬件设备在Linux操作系统中通常被表示(映射)为文件。例如:作为超级用户,你可以通过如下命令将CD-ROM驱动器挂载为一个文件:
# mount -t iso9660 /dev/hdc /mnt/cdrom
# cd /mnt/cdrom
这个命令将CD-ROM设备(在开机引导时被加载为/dev/hdc)中的当前内容挂载为/mnt/cdrom目录下的文件结构。然后,你就可以像操作普通文件那样在CD-ROM目录中漫游,只不过该目录中的内容是只读的。
UNIX和Linux中比较重要的设备文件有三个-----(1) /dev/console (2) /dev/tty (3) /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 <filename>命令,它的作用是改变文件的修改时间,如果指定名字不存在,就创建一个新文件,但它并不会把有内容的文件变成空文件。 |
3.系统调用和设备驱动程序
操作系统的核心部分,即内核。是一组设备驱动程序。这是一些对系统硬件进行控制的底层接口。例如:磁带机就有一个与之对应的设备驱动程序,他知道如何启动磁带,如何对它进行前后回绕、如何对它进行读写等。它还知道磁带必须以固定长度的数据块为单位进行读写。因为磁带上的数据读写操作完全是线性的,所以该驱动并不能直接访问磁带上的数据块。而是必须先把它回绕到正确的位置。
类似的,一个底层的硬盘设备驱动程序一次只能读写一整块硬盘扇区,但却能够直接存取硬盘上任意位置上的数据,因为硬盘是一种随机存储设备。
为了向用户提供一个统一的接口,设备驱动程序封装了所有与硬件相关的特性。硬件的特有功能可通过ioctl完成。
/dev目录中的设备文件的用法都是一致的,它们都可以被打开、读、写和关闭。例如:用来访问普通文件的open调用同样可以用来访问一台用户终端、一台打印机设备或一台磁带机。
用来访问设备驱动程序的底层函数(系统调用)包括:
open:打开文件或设备
read:从打开的文件或设备里读数据
write:向文件或设备写数据
close:关闭文件或设备
ioctl:把控制信息传递给设备驱动程序
系统ioctl用来提供一些与特定硬件设备有关的必要控制(与正常输入输出相反),所以它的用法随设备的不同而不同。例如:ioctl调用可以用于回绕磁带机或设置串行口的流控特性。因此,ioctl并不需要具备可移植性。此外,每个驱动程序都定义了自己的一组ioctl命令。
4.库函数
在输入输出操作中,直接使用底层系统调用的问题使它们的效率非常低。为什么呢?
& 系统调用会影响系统的性能。与函数调用相比,系统调用的开销要大些,因为在执行系统调用时,Linux必须从用户代码切换到内核代码运行,然后再返回用户代码。减少这种开销的一个好方法是,在程序中尽量减少系统调用的次数,并且让每次系统调用完成尽可能多的工作。
例如:每次读写大量的数据而不是每次仅读写一个字符。
& 硬件会对底层系统调用一次所能读写的数据块做出一定的限制。例如:磁带机通常的写操作数据块的长度为10k,所以如果卸的数据量不是10k的整数倍,磁带机还是会以10k为单位卷绕磁带,这就在磁带上留下了空隙。
为了给设备和磁盘文件提供更高层的接口,与Unix一样,Linux发行版提供了一系列的标准函数库。它们是一些由函数构成的集合,你可以把它们包括在自己的程序中去处理呢些与设备和文件有关的问题。提供输出缓冲功能的标准IO库就是一个这样的例子。你可以高效地写任意长度的数据块,库函数则在数据满足数据块长度要求时安排执行底层系统调用。这就极大的降低了系统调用的负面影响。
库函数会有一个与之相对应的标准头文件,例如:与标准IO库对应的头文件stdio.h
图:显示了Linux系统中各种文件函数与用户、设备驱动程序、内核和硬件之间的关系。
5.底层文件访问
每个运行中的程序被称为进程(process),它有一些与之相关联的文件描述符。这是一些小值整数,你可以通过它们访问打开的文件或设备。有多少文件描述符可用取决于系统的配置情况。当开始运行程序时,它一般会有三个已经打开的文件描述符。它们是:
0:标准输入
1:标准输出
2:标准出错
二. Linux中文件编程可以使用两种方法:
(1) Linux 系统调用
(2) C语言库函数
前者依赖于Linux系统,后者与操作系统是独立的,在任何操作系统下,使用C语言库函数操作文件的方式都是相同的。
系统调用open () ,read(),write(),lseek();
库函数fopen(), fread(),fwrite(),flseek();
库函数
1.fopen()函数
函数功能 | 打开文件 |
头文件 | #include<stdio.h> |
函数原型 | FILE *fopen(const char *path ,const char *mode) |
参数说明 | §path:欲打开的文件路径与文件名 §mode:打开方式: “r”:以只读方式打开文件 “r+”:以可写可读方式打开文件 “w”:以只写方式打开文件 “w+”:以可读可写的方式打开文件,若文件不存在则建立文件 “a”:以追加的方式打开文件,若文件不存在则建立文件; 若文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。 “a+”:相对“a”功能的基础上,以追加方式打开可读可写的文件。 |
返回值 | 文件打开成功,返回文件指针; 文件打开失败,则返回NULL,并把错误码存在errno中 |
范例: #include<stdio.h> Main() { FILE *fp; fp=fopen(“hell.txt”,”a+”); if(fp==NULL) return: fclose(fp); } |
1. open()函数
所需头文件 | #include <sys/types.h> #include <sys/stat.h > #include <fcntl.h>
| |
函数原型 | (1)Int open(const char *pathname, int flags); (2)Int open(const char *pathname, int flags, mode_t mode ); | |
区别当flag使用了O_CREAT标志,则使用第二种形式,这时需要mode来指定访问权限
| ||
函数传入值 | Pathname | §Pathname:要打开的文件路径和文件名
|
§flags:打开方式,必须从下面三个中选一个: | O_RDONLY:只读模式; | |
O_WRONLT:只写模式 | ||
O_RDWR:读写模式; | ||
§flags:另外,还可以附加选项,通过与上面3个选项通过”|”连接 | O_APPEND:每次写操作都写入文件的末尾; | |
O_CEAT:如果指定文件不存在,则创建这个文件; | ||
O_EXCL:如果要创建的文件已存在,则返回-1,并且修改errno的值,需要与O_CREAT配对使用以确保文件是新建的。 | ||
O_TRUNC:如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(即长度截短为0)。 | ||
O_NOCTTY:如果路径名指向终端设备,不要把这个设备用作控制中端; | ||
函数返回值:成功返回文件描述符; 失败返回:-1; |
范例:
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
main()
{
int fd,size;
char s [ ]=”Linux Programmer!\n”,buffer[80];
fd=open(“/tmp/temp”,O_WRONLY|O_CREAT);
write(fd,s,sizeof(s));
close(fd);
fd=open(“/tmp/temp”,O_RDONLY);
size=read(fd,buffer,sizeof(buffer));
close(fd);
printf(“%s”,buffer);
}
2.fseek()函数
函数功能 | 移动文件流的读写位置 |
头文件 | #include<stdio.h> |
函数原型 | Int fseek(FIEL * stream , long offset , int origin) |
参数说明 | §stream:文件指针 §offset:偏移量,正数表示正向偏移,负数表示负向偏移 §origin:设定从文件的哪里开始偏移,可能取值为: SEEK_SET:文件开头 SEEK_CUR:当前位置 SEEK_END:文件结尾 |
返回值 | 成功,返回0; 失败,返回-1; |
范例: #include<stdio.h> Long filesize(FILE *stream) { FILE *stream; Stream=fopen(“MYFILE.txt”,“w+”); fprintf (stream ,“this is a test”); printf(“filesize of MYFILE.txt is %ld bytes\n” ,filesize(stream) ); return 0; } Long filesize(FILE *stream) { Long curpos ,length; Curpos=ftell(stream); Fseek(stream, 0L ,SEEK_END); Length=ftell(stream); //*ftell(fd)求文件总长度 fseek=(stream ,curpos ,SEEK_SET); return length; } |
2. lseek()函数
函数功能 | 移动文件读写指针 |
头文件 | #include <sys/types.h> #include <unistd.h> |
函数原型 | Off_t lseek(int fd, off_t offset, int whence) |
参数说明 | §Fd:文件描述符 §offset:偏移量,正数表示正向偏移量,负数表示负向偏移量 §whence:设定从文件的哪里开始偏移,可取值为: SEEK_SET:文件开头 SEEK_CUT:当前位置 SEEK_END:文件结尾 (其中SEEK_SET、SEEK_CUT、SEEK_END:0,1和2) |
返回值 | 当调用成功则返回目前的读写位置相对头文件的偏移量,(即距文件开头多少字节,若有错误则返回-1,errno会存放错误代码) |
范例 | 1. 欲将读写位置移到文件开头时: lseek(int fd, 0, SEEK_SET) 2. 欲将读写位置移到文件结尾时: lseek(int fd, 0, SEEK_CUT) 3. 欲将读写位置移到当前位置时: lseek(int fd, 0, SEEK_END) |
3.fread()函数
函数功能 | 从一个流中读数据 |
头文件 | #include<stdio.h> |
函数功能 | Int fread(void *ptr ,int size ,int nitems, FILE *stream) |
函数说明 | §ptr:用于接收数据的地址(指针) §size:每个字段中所含字节数 §nitems:要读取的总字段数; §stream:提供数据的文件指针 |
返回值 | 成功返回读取的元素个数 |
范例:
|
3.read()函数
函数功能 | 由打开的文件读写数据 |
头文件 | #include <unistd.h> |
函数原型 | Ssize_t read(int fd, void *buf , size_t count) |
参数说明 | §fd:文件描述符 §buf:存放读取数据的缓冲区 §count:读取的最大长度(字节数) |
返回值 | 成功返回实际读取的字节数,失败返回-1 |
4.write()函数
函数功能 | 向文件写入一个数据块 |
函数原型 | Ssize_t write(int fd ,const void *buf ,size_t count) |
参数说明 | §fd:文件描述符 §buf:写入数据的缓冲区 §count:写入数据的最大长度(总字节数) |
返回值 | 成功返回实际写入字节数,当有错误发生时则返回-1,错误代码存入errno |
4.fwrite()函数
函数功能 | 向文件写入一个数据块 |
头文件 | #include<stdio.h> |
函数功能 | Int fwrite(const void* buffer ,size_t size , size_t count, FILE *stream) |
函数说明 | §buffer:用于输入的数据的地址(指针) §size:每个字段中所含字节数 §nitems:要读取的总字段数; §stream:提供数据的文件指针 |
返回值 | 成功返回写入的元素个数 |
其注意点和其他的一些对比 1.文件描述符的对比:区别: 系统调用:中开打文件返回的是一个整型数;文件描述符是整数 库函数:中是用FILE 类型的指针来代表文件,文件描述符是FILE类型的指针 2.b用于区分二进制文件和文本文件,这一点在DOS、Windows系统中式有区分的,但linux不区分二进制文件和文本文件。
|
****库函数方式访问文件***
库函数---格式化读
fscanf (FILE *stream ,char * format[,argument…])
从一个流中进行格式化输入
(//format是格式,流就是文件;从文件中进行格式化的输入,实际上就是一个读取)
#include <stdib.h>
#include <stdio.h>
int main(void)
{ int i;
printf(“input an integer ”);
if(fscanf(stdin, “%d”,&i)) // 从标准输入(键盘) 以%d(整数)的形式写入到 &i的地址空间中
{
printf(“the integer read was : %i \n” ,i);
return 0;
}
}
库函数---格式化写
Int fprintf(FILE *stream ,char *format[,argument,…])
格式化输出到一个流中
#include <stdio.h>
#include <process.h>
FILE *stream;
Void main()
{
int i=10;
double fp=1.5;
char s[]=”this is a string”;
char c=’\n’;
stream =fopen(“fprintf.out ”, “w”);
fprintf(stream ,”s%c%”, s, c); //把s数组中的字符串写入到stream—即fprintf.out 文件中
fprintf(stream,“%d\n”, i); //把i整数写入stream—即fprintf.out文件中
fprintf (stream, “%f\n”, fp); //把fp值以浮点数的格式写入fprintf.out文件中
fclose (stream);
}
库函数—定位
int fseek (FILE *stream, long offset , int whence)
whence :(基准点)
SEEK_SET 从文件的开始处开始搜索
SEEK_CUR 从当前位置开始搜索
SEEK_END 从文件的结束处开始搜索
路径获取:
在编写程序的时候,有时需要得到当前路径。C库函数提供了getcwd来解决这个问题。
Char *getcwd(char *buffer ,size_t size)
我们提供一个size 大小buffer , getcwd 会把当前的路径名copy到buffer中。如果buffer太小,函数会返回 -1.
例:
#include <unistd.h>
main()
{
char buf [80];
getcwd(buf,sizeof(buf));
printf(“current working directory:%s\n”,buf);
}
创建目录:
#include <sys/stat>
int mkdir(char *dir ,int mode)
功能:创建一个新目录
返回值:0表示:成功; -1表示:失败;
***系统调用方式访问文件***@系统调用---文件访问
系统调用---创建
int creat (const char *filename ,mode_t mode)
(1)filename :要创建的文件名(包含路径,缺省为当前路径)
(2)mode :创建模式
常见创建模式:
S_IRUSR 可读
S_IWUSR 可写
S_IXUSR 可执行
S_IRWXU 可读、可写、可执行
除了可以使用上述宏以外,还可以直接使用数字来表示文件的访问权限:
(1) 可执行à1
(2) 可写à2
(3) 可读à 4
上述值的和,如可写、可读à6 ; 无任何权限à0 ; 可读、可执行à5
第二次观看,附加:
权限0755 是什么意思?
0 :只是一个占位符;
7 :“文件的所有者”拥有可读、可写、可执行
5 :“文件所有者所在的组”,拥有可读、可执行
5 :“其他用户”拥有可读、可执行
Gcc hello .c –o hello
如果没有用 –o 去指定输出应用程序的名字,则输出的应用程序默认为 a .out
文件描述符:
在Linux系统中,所有打开的文件都对应一个文件描述符。文件描述符的本质是一个非负整数。当打开一个文件时,该整数由系统来分配。问件描述符的范围是0~OPEN_MAX。(早期的UNIX版本OPEN_MAX=19),即允许每个进程同时打开20个文件,现在很多系统则将其增加到1024.
例:代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void create_file(char *filename){
/*创建的文件具有什么样的属性?*/
if(creat(filename,0755)<0){
printf("create file %s failure!\n",filename);
exit(EXIT_FAILURE);
}else{
printf("create file %s success!\n",filename);
}
}
int main(int argc,char *argv[])
{
int i;
if(argc<2){
perror("you haven't input the filename,please try again!\n");
//perror();函数打印出错原因信息字符串
exit(EXIT_FAILURE);
}
for(i=1;i<argc;i++){
create_file(argv[i]);
}
exit(EXIT_SUCCESS);
}
系统调用---定位
int lseek(int fd ,offset_t offset , int whence)
功能:将文件读写指针对whence移动offset个字节。操作成功时,返回文件指针相对于头文件的位置。
(1) whence :表示从什么地方开始移动指针(基准点)
(2) 先前移动3个字节offset= -3;
Whence可使用下述值:
SEEK_SET:相对文件开头
SEEK_CUR :相对文件读写指针的当前位置
SEEK_END:相对文件末尾
Offset可取负值,表示向前移动。例如下述调用可将文件指针相对当前位置向前移动5个字节:
Lseek (fd ,-5 ,SEEK_CUR)
如何利用lseek来计算文件长度?
由于lseek函数的返回值为文件指针相对于文件头位置,因此下面调用的返回值就是文件的长度:
lseek(fd ,0 ,SEEK_END)
(因为操作成功时,返回文件指针相对于头文件的位置)
系统调用----访问判断
有时我们需要判断文件是否可以进行某种操作(读,写等),这时可以使用access函数:
int access(const char *pathname ,int mode)
pathname:文件名称
mode: 要判断的访问权限。可以取以下值或者是他们的组合。
R_OK :文件可读 W_OK :文件可写 X_OK :文件可执行;F_OK文件存在
返回值:当我们测试成功时,函数返回0,否则如果一个条件不符合,返回 -1;
例程:(判断文件是否可读)
#include <unistd.h>
int main ()
{
If (access(“/etc/passwd”, R_OK)==0)
printf(“/etc/passwd can be read!\n”);
}