您的位置:首页 > 运维架构 > Linux

Linux环境下C语言学习

2012-12-09 15:16 239 查看
#include
#include
#include
#include

#define MAX 1024

int main(int argc, char * argv[]){
char buf[MAX];
int in, out;
int n;

if(argc < 3){
printf("arguments lack.\n");
exit(1);
}

if((in = open(argv[2], O_RDONLY)) == -1){
perror("fail to open src file");
exit(1);
}

if((out = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC)) == -1){
perror("fail to open target file");
exit(1);
}

while((n = read(in, buf, MAX)) > 0){
if(write(out ,buf, n) != n){
perror("fail to write");
exit(1);
}

printf("copy done.\n");

close(in);
close(out);

return 0;
}
}


17.2.9 文件同步

上一小节讨论了文件读写的内核机制,由于文件的读写操作会由于缓冲的缘故导致使输出延时,所以在一段时间内,会导致内存中的文件内容和外存上的文件内容不一直。

为了避免这种情况,用户可以指定系统在缓冲区并未填满的时候将文件内容回写到磁盘上。这种操作叫做文件同步,Linux提供以下函数用于文件同步操作,其函数原型如下:

#include

int fsync(int files);

int fdatasync(int files);

void sync(void);

sync函数所做的操作最简单,该函数将所有打开的文件回写到磁盘上,将文件中修改过内容的盘块放入系统列队后就返回,并不等待磁盘实际写入外存。因此,该函数能够加快文件同步速度,但是并不能保证真正的文件同步。

fsync函数可以确保文件的实际写出,该函数会阻塞知道修改的盘块写到外存后才返回,成功返回0,如出错返回-1。

注意:fsync函数是需要保持同步文件的文件描述符,fsync函数可以保证文件内容及时更新,并且不用频繁做打开关闭文件的操作。

下面的实例演示了使用fsync函数进行文件的同步操作。该程序首先打开一个文件,之后每隔5秒向文件输出一行字符。输出字符后程序调用fsync函数将输出的内容回写到磁盘上。用户可以打开该文件,观察输出的信息是否机试回写到磁盘文件上。

fsync.c使用fsync函数进行文件同步

#include
#include
#include
#include
#include

int main(void){
int fd;
int i;
char * file = "test.txt";

fd = open(file, O_WRONLY | O_APPEND);
if(fd == -1){
perror(strcat("fail to open file : ", file));
exit(1);
}

i = 0;
char * content = "Hello world.\n";
while(i++ < 3){
sleep(5);

printf("%s", content);
if(write(fd, content, strlen(content)) == -1){
perror("fail to write");
exit(1);
}

if(fsync(fd) == -1){
perror("fail to fsync");
exit(1);
}
}

close(fd);

return 0;
}


fdatasync函数和fsync函数类似,但不同的是fdatasync函数只更新文件的数据部分,而不更新文件的属性部分。fdatasync函数的参数和返回值与fsync函数的意义相同。而sync函数将所有打开文件的内容都回写到磁盘上,所以该函数不需要任何参数,也不返回任何值。

17.3 文件描述符操作

17.2节讨论了文件的基本I/O,后面的几个小节讨论关于文件描述符操作,这些操作不操作文件本身,而是操作文件与进程的联结点——文件操作符,如果读者对文件描述符的本质有些遗忘,请回顾本章17.1节的内容。

17.3.1 复制文件描述符

Linux环境下可以使用dup函数和dup2函数复制一个文件描述符,其函数原型如下:

#include

int dup(int files);

int dup2(itn files, int files2);

dup函数的参数是需要复制的文件描述符,该函数总是找到进程文件表中第1个可用的文件描述符,将参数指定的文件描述符复制到该描述符后返回该描述符。例如,一个进程创建后默认打开了3个文件——分别是标准输入、标准输出和标准出错,3个文件的文件描述符分别是0、1和2。所以这时执行如下操作:

fd = dup(0);

这时fd的值是第1个可用的文件描述符,也就是除了0、1和2之外的第1个描述符,本例中为3。复制后的文件描述符公用一个文件表项,所以两个文件的偏移量、文件状态标志等都是共享的。

注意:由于文件表项是共享的,所以,下面利用dup函数判断当前进程第1个可用的文件描述符是多少的用法是错误的。

#include

int first_avail_fd(){

int fd;

fd = dup(0);

close(fd);

return fd;

}

注意:以上程序的错误在于如果关闭fd所代表的文件,那么标准输入同时也被关闭了,因为这2个文件公用一个文件表项。

所以,正确的写法如下:

#include

int firest_avail_fd(){

int fd;

fd = open("test.txt", O_RDONLY);

close(fd);

return fd;

}

dup2函数和dup函数类似,不过dup函数允许用户使用第2个参数指定将文件描述符复制到哪一个文件描述符上(dup2实际上应该读成dup to而不是dup two)

int dup2(fd1, fd2);

该操作表示将fd1复制到fd2上,并且返回fd2的值。如果fd2已经打开,则将fd2所代表的文件关闭,再将fd1复制到fd2。例如:以下使用情况:

fd = dup2(fd1, fd1);

该种使用是很危险的,在复制时会关闭fd1所代表的文件,这时复制过来的fd1文件描述符将是一个无意义的值,对其操作会导致程序出错。所以在使用dup2函数时应做一下判断:

if(fd1 != fd2){

fd = dup2(fd1, fd2);

}

17.3.2 I/O重定向概念及其应用

当一个程序运行时,有三个文件总是被打开的。其三个文件分别是标准输入、标准输出和标准出错。标准输入就是通常所说的键盘输入,而标准出错和标准输出就是通常所说的屏幕输出。

重定向的概念就是使用一个文件代替系统默认的标准输入、标准输出和标准出错。例如,使用test.txt文件进行重定向输入,就是用test.txts代替原来的标准输入。这时应用程序不再从键盘输入中读取输入内容,而是从test.txt文件中读入输入内容。同样,使用test.txt文件重定向标准输出,就是使用test.txt文件代替原来的标准输出。这时应用程序将输出的内容输出到文件test.txt中。

Linux环境下,可以在shell中实现最基本的I/O重定向。"filename"表示标准出错重定向,程序将出错信息输出到filename文件上,而不再是输出到屏幕上。下面以ls命令距离重定向最基本的应用。

在编程过程中I/O重定向也是很有用的。下面实例演示了一个I/O重定向的应用。该程序需要从input.txt文件中读入10个数据,并且找到其中最大值,并且将该最大值输出到res.txt文件中。该程序编写过程中并不进行文件操作,而是将文件操作的细节交给I/O重定向进行处理。

get_max.c取得十个数据的最大值

#include

#define MAX 10

int main(void){

int array[MAX];

int i;

int max;

for(i = 0; i < MAX; i++){

scanf("%d", &array[i]);

}

max = array[0];

for(i = 0; i < MAX; i++){

if(array[i] > max){

max = array[i];

}

}

printf("the result is %d.\n", max);

return 0;

}

./get_max < input.txt > res.txt

该方法的优点很明显,程序不再关注文件操作的细节,编程难度降低了。同时该方法的缺点也很明显,首先,其输入过于复杂,(需要5个命令行参数),过多的命令参数是程序的使用复杂度增加了。

解决的办法是将重定向的命令输入封装到一个shell脚本中,用户只需要执行该shell脚本就可以了。这时,用户不关心如何执行该程序的细节,只需要输入shell脚本的命令就可以了。

使用I/O重定向操作文件输入输出的第2个缺点是该中方法过于依赖shell。假如程序运行在某些shell的系统上时,例如,某些嵌入式系统时,该方法就不能使用了。代替的方法是在程序中使用C语言函数库或者Linux系统提供的系统调用接口实现文件操作。

17.3.3 控制文件

当用户需要对一个打开文件的属性进行修改,或者得到该文件中的某些属性时,可以使用fcntl函数。该函数可以看作是文件操作的一个集合。几乎所有的属性操作都可以通过调用fcntl函数实现。该函数的每一个功能都可以找到一个其他系统函数的替代。Linux系统环境使用fcntl函数进行文件属性的操作,其函数原型如下:

#include

int fcntl(int files, int cmd, ...);

fcntl函数的第1个参数表示需要进行属性操作的文件描述符。该文件必须是一个i额已经打开的文件。fcntl函数的第2个参数表示可以执行的命令,每一个命令代表一个不同的功能。这些命令有10个,分为5中功能。

F_DUPFD 复制文件描述符

F_GETFD/F_SETFD 获得/设置文件描述符标志

F_GETEL/F_SETEL 获得/设置文件状态标志

F_GETOWN/F_SETOWN 获得/设置异步I/O所有权

F_GETLK/F_SETLK/F_SETLKW 获得/设置记录锁

fcntl函数的第3个参数通常是一个整数,其意义根据第2个参数的不同而不同。也就是说,第3个参数的意义是有fcntl函数选择的命令决定的。

17.3.4 修改打开文件的文件状态

fcntl命令中比较常用的是设置文件状态标志的命令F_SETFL,以及获取文件状态标志的命令F_GETFL。由于文件的状态标志在调用open函数打开文件时已经确定,当需要修改这些标志时就只能调用fcntl函数。但是,并不是所有的文件状态标志都可以被fcntl函数修改。

下面实例演示了使用fcntl函数修改文件的状态标志。该程序首先打开一个文件,之后使用fcntl函数将文件的状态标志修改为追加写,向文件中邪的内容追加到文件的结尾。

#include

#include

#include

#include

#include

int main(void){

int fd;

int flag;

char *p = "1st linux";

char *q = "2nd linux";

fd = open("test.txt", O_WRONLY);

if(fd == -1){

perror("fail to open");

exit(1);

}

if(write(fd, p, strlen(p)) == -1){

perror("fail to write");

exit(1);

}

flag = fcntl(fd, F_GETFL, 0);

if(flag == -1){

perror("fail to fcntl");

exit(1);

}

flag |= O_APPEND;

if(fcntl(fd, F_SETFL, flag) == -1){

perror("fail to fcntl");

exit(1);

}

if(write(fd, q, strlen(q)) == -1){

perror("fail to write");

exit(1);

}

close(fd);

return 0;

}

首先写入的ls linux覆盖了部分原文件内容,而第2次写入的2nd linux则追加到文件的结尾。

17.4 非阻塞文件I/O

操作系统通过I/O与外部设备进行交互,速度较慢的I/O会造成读写函数阻塞。非阻塞I/O适用于低速的外部设备,例如,屏幕输出、网络文件系统等。这里所说的低速是相对于计算机的处理速度而言的。本章将详细讲解非阻塞的文件I/O。

例如,一个应用程序调用read函数从远程的文件系统中读取数据,这时需要传输的数据由于网络连接的问题导致传输速度很慢;或者在传输过程中由于某些意外,需要的数据丢失了。此时应用程序调用的read函数就会因为读取不到数据而阻塞,知道需要的数据可以读写。

为了防止I/O操作不必要的阻塞造成应用程序不能正常执行,Linux系统允许用户以非阻塞的方式打开一个文件。当使用该方式打开文件后,如果遇到需要读写的外部设备的数据部可用,I/O操作函数不会阻塞而是返回,并将errno变量设置为EAGAIN。该错误号表示I/O系统调用没有阻塞等待数据,导致本次读写操作失败。

17.4.2 以非阻塞方式打开文件

Linux环境下可以在调用open函数打开一个文件的同时使用O_NONBLOCK选项将打开的文件设置为非阻塞方式。下面的实例演示了非阻塞方式的I/O操作。该程序运行的格式为:

程序名 读入的大文件的文件名 存放输出结果的文件名

该程序打开一个很大的文件并将其内容读入,之后输出到test.txt文件上。test.txt文件是一个低速的外部设备,在对其进行I/O操作的时候经常会遇到设备暂时不可用的情况。

由于需要在打开test.txts文件的时候设置O_NONBLOCK选项,这时I/O操作不会阻塞,而是返回后将errno变量设置为EAGGIN。该程序读入一个大文件,并且将因为非阻塞而返回的出错情况输出到另一个文件上便于用户观察。

17.4.3 将一个打开的文件设置为非阻塞方式

对于一个已经打开的文件,Linux系统同样可以将其设置为非阻塞方式。非阻塞方式被实现为文件描述符中的一个文件状态。

说明:由于需要修改一个已经打开的文件状态标志,这时需要使用fcntl函数对此进行操作。

fcntl函数可以修改一个打开文件的文件状态标志,使用的命令(fcntl函数的第2个参数)是F_SETFL和F_GETFL。首先使用F_GETFL命令得到文件的状态标志,之后修改相应的位。最后修改后的文件状态标志用F_SETFL命令设置为新的文件状态。

下面的实例演示了一个已经打开的文件设置为非阻塞方式。使用fcntl函数不需要关闭再打开标准输出,只需要使用F_GETFL和F_SETFL命令对其进行设置。

17.5 内存映射I/O

提出内存映射I/O概念的根本原因是为了解决读写文件效率问题。如果一个程序需要大量的磁盘I/O时,内存映射I/O往往能够使程序执行的速度有很大的提高,内存映射I/O也可以算作是一种空间换时间的机制。

17.5.1 内存映射I/O的概念

单用户对一个文件执行I/O操作时,通常需要使用一个内核的缓冲区。输入或者输出的内容先通过该缓冲区和外部设备发生数据交换。

注意:文件读写函数的缓冲区大小是由内核确定的,用户无法改变。

如果需要读写一个大于该缓冲区大小的文件则需要多次与外部设备发生交互,因此导致了该程序运行速度的损失。Linux系统下可以使用内存映射I/O来解决问题。

内存映射I/O是一种高速读写文件的I/O方式,其允许将一个磁盘文件与内存中的一个缓冲区建立一个映射关系。当用户从该缓冲区中读取数据的时候,就相当于从映射的文件中读取数据,当用户向该缓冲区写入数据的时候,就相当于想映射的文件输出数据。

这种方式可以在不使用read函数和write函数的情况下执行文件I/O操作,从而可以避免由于内核提供的缓冲区过小而造成的I/O效率瓶颈。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: