2.7 fcntl——处理文件的共享问题,调用成功后返回0,或者如果失败,则设置errno变量并返回-1。
2.7.1 fcntl函数说明
主要用于文件已经共享的时候如何时操作,也就是当多个用户共同使用、操作一个文件的情况,这时,Linux通常采用的方法是给文件上锁,来避免共享的资源产生竞争的状态。
文件锁包括建议锁和强制锁。建议锁要求每个上锁文件的进程都要检查是否有锁存在,并且尊重已有的锁。在一般情况下,内核和系统不使用建议锁。强制锁是由内核执行的锁,当一个文件被上锁进行写入操作的时候,内核将阻止其他任何文件对其进行读写操作。采用强制锁对性能的影响很大,每次读写操作都必须检查是否有锁存在。
在Linux中,实现文件上锁的函数有flock和fcntl,其中flock用于对文件施加建议性锁,而fcntl不仅可以施加建议锁,还可以施加强制锁。同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁。
记录锁又可分为读取锁和写入锁,其中读取锁又称为共享锁,它能够使多个进程都能在文件的同一部分建立读取锁。而写入锁又称为排斥锁,在任何时刻只能有一个进程在文件的某个部分上建立写入锁。当然,在文件的同一部分不能同时建立读取锁和写入锁。
2.7.2 fcntl函数格式
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd , int cmd , struct flock *lock);
fd为文件描述符;
cmd可取的命令为:
F_DUPFD:复制文件描述符
F_GETFD:获得fd的close-on-exec标志,若标志未设置,则文件经过exec函数之后仍保持打开状态。
F_SETFD:设置close-on-exec标志,该标志以参数arg的FD_CLOEXEC位决定。
F_GETFL:得到open设置的标志
F_SETFL:改变open设置的标志
F_GETFK:根据lock描述,判断能否上文件锁:如果可以上锁,将lock域中的l_type改写为F_UNLCK,其它部分保持不变;如果不能上锁,则返回锁中关于 the l_type, l_whence, l_start, and l_len的细节,并将I_pid设置为持有锁的进程的PID。
F_SETFK:设置lock描述的文件锁
F_SETLKW:这是F_SETLK的阻塞版本(命令中的W表示等待wait)。如果存在其他锁,则调用进程睡眠;如果捕捉到信号则睡眠中断。
F_GETOWN:检索将收到SIGIO和SIGURG信号的进程号或进程组号
F_SETOWN:设置进程号或进程组号
lock:结构为flock,设置记录锁的具体状态。
这里lock的结构如下:
struct flock
{
short l_type;
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}
lock结构中,每个变量的取值含义为:
l_type:
F_RDLCK:读取锁(共享锁)
F_WRLCK:写入锁(排斥锁)
F_UNLCK:解锁
l_start:相对位移量
l_whence:相对位移量的起点,同lseek中的whence
SEEK_SET:当前位置为文件开头,新位置为偏移量的大小。
SEEK_CUR:当前位置为文件读写指针的位置,新位置为当前位置加上偏移量。
SEEK_END:当前位置为文件末尾,新位置为文件的大小加上偏移量的大小。
小技巧:为加锁整个文件,通常的方法是将l_start设置为0,l_whence说明为SEEK_SET,l_len说明为0。
2.7.3 fcntl使用实例
下面首先给出了使用fcntl函数的文件记录锁函数。在该函数中,首先给flock结构体的对应位赋予相应的值。接着使用两次fcntl函数分别用于给相关文件上锁和判断文件是否可以上锁,这里用到的cmd的值分别是F_SETLK和F_GETLK。
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
/*lock_set函数 */
void lock_set(int fd, int type)
{
struct flock lock;
lock.l_whence=SEEK_SET;
lock.l_start=0;
lock.l_len=0;
while(1)
{
lock.l_type=type;
/*根据不同的type值给文件上锁或解锁*/
if((fcntl(fd,F_SETLK,&lock))==0)
{
if(lock.l_type==F_RDLCK)
printf("read lock set by %d ",getpid());
else if(lock.l_type==F_WRLCK)
printf("write lock set by %d ",getpid());
else if(lock.l_type==F_UNLCK)
printf("release lock by %d ",getpid());
return;
}
/*判断文件是否可以上锁*/
fcntl(fd,F_GETLK,&lock);
/*判断文件不能上锁的原因*/
if(lock.l_type!=F_UNLCK)
{
if(lock.l_type==F_RDLCK)
printf("read lock already by %d ",lock.l_pid);
else if (lock.l_type==F_WRLCK)
printf("write lock already by %d ",lock.l_pid);
getchar();
}
}
}
int main(void)
{
int fd = open("hello.txt",O_RDWR|O_CREAT,0666);
if(fd<0)
{
perror("open:");
return 1;
}
/*给文件写入锁*/
//lock_set(fd,F_WRLCK); /*选择其中一个*/
lock_set(fd,F_RDLCK);
getchar();
/*给文件解锁*/
lock_set(fd,F_UNLCK);
getchar();
if(close(fd)<0)
{
perror("close:");
return 1;
}
return 0;
}
$ gcc fcntl_write.c -o fcntl_write
运行分两种
1). 在一个终端下运行:
$ ./fcntl_write
write lock set by 3490
release lock by 3490
2). 分别在两个终端下运行:
$1 ./fcntl_write
write lock set by 3490
$2 ./fcntl_write
write lock already by 3490
$1 Enter
release lock by 3490
$2 Enter
write lock set by 3493
release lock by 3493
由此可见,写入锁为互斥锁,同一时刻只能有一个写入锁存在。
当使用读取锁时,再分别在两个终端下运行:
$1 ./fcntl_write
read lock set by 3623
release lock by 3623
$2 ./fcntl_write
read lock set by 3637
release lock by 3637
2.8 改变文件大小,可用于清空文件内容
ftruncate()函数
函数功能:改变文件大小
相关函数:open、truncate
表头文件:#include <unistd.h>
函数原型:int ftruncate(int fd, off_t length)
函数说明:ftruncate()会将参数fd指定的文件大小改为参数length指定的大小。参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件。如果原来的文件件大小比参数length大,则超过的部分会被删去
返 回 值:0、-1
错误原因:errno
EBADF 参数fd文件描述词为无效的或该文件已关闭
EINVAL 参数fd为一socket并非文件,或是该文件并非以写入模式打开
使用方法:fd一般可以fileno(FILE *fp)获取,标示文件当前的大小,length则可由用户定义。此函数一般用在文件初始化或者重新为文件分配空间时。
ftruncate函数在文件清空方面作用很大
对一个文件用读写方式打开 fopen("...", "r+");首先读出文件里面的(9php.com)内容,处理完成后需要重新写入文件中。在重新写入的(9php.com)时候需要先清空原来文件里面的(9php.com)内容,如何实现此处的(9php.com)清空?
原来使用先以读方式打开,读入缓冲后关闭文件,然后再以写打开,这样做两遍打开关闭,太浪费时间。
用ftruncate可以清空文件,如:ftruncate(fileno(fp), 0);
清空文件后,需要使用rewind或fseek将文件指针移到文件头。
此函数并未实质性的向磁盘写入数据,只是分配了一定的空间供当前文件使用。当fd<length时,此时如果使用十六进制编辑工具打开该文件,你会发现文件末尾多了很多00,这就是执行这个函数后的效果。如果发生系统复位或者装置掉电以后,该函数所产生的作用将被文件系统忽略,也就是说它所分配的空间将不能被识别,文件的大小将会是最后一次写入操作的区域大小,而非ftruncate分配的空间大小,也就是说,文件大小有可能会被改变。
解决方法:可以在执行完ftruncate之后,在新空间的末尾写入一个或以上字节的数据(不为Ox00),这样新空间则不为空,文件系统会把这部分空间当成这个文件的私有空间处理,而不会出现文件大小改变的错误。
2.9 ioctl——通常来说,成功操作是返回0,但极少数ioctl()请求使用返回值作为输入参数,并返回一个非负数。错误时,返回-1并设置errno变量。
#include <sys/ioctl.h>
int ioctl(int d , int requets, ...);
用于设置或检索文件的多种有关参数并对文件进行一些其他的操作。参数d必须是一个打开的文件描述符,参数requets是一个依赖于设备的请求代码。第三个参数是一个无类型的指向内存的指针。
2.10 select——处理I/O复用的情况,
2.10.1 函数说明
总的来说,I/O处理的模型有5种:
* 阻塞I/O模型:在这种模型下,若所调用的I/O函数没有完成相关的功能就会使进程挂起,直到相关数据到才会出错返回。如常见对管道设备、终端设备和网络设备进行读写时经常会出现这种情况。
* 非阻塞模型:在这种模型下,当请求的I/O操作不能完成时,则不让进程睡眠,而且返回一个错误。非阻塞I/O用户可以调用不会永远阻塞的I/O操作,如open、write和read。如果操作不能完成,则会立即出错返回,且表示I/O如果该操作继续执行就会阻塞。
* I/O多路转接模型:在这种模型下,如果请求的I/O操作阻塞,且它不是真正的阻塞I/O。
3. C库函数的文件操作
C库函数的文件操作实际上是独立于具体的操作系统平台的,不管是在DOS、Windows、Linux还是在VxWorks中都是这些函数。
3.1 创建和打开——fopen返回一个文件指针,该指针可以传递给别的标准I/O函数来标识这个流。文件指针指向一个描述流的结构。在出现错误的情况下,fopen返回NULL并把errno变量设为恰当的值。如果用fopen打开供写入数据的文件不存在,那么它会以权限0666来创建该文件,这和用进程的umask来设置权限的情况类似。
#include <stdio.h>
FILE *fopen(const char *path , const char *mode);
fopen()实现打开指定文件filename,FILE * 标识出一个文件指针,其中的mode为打开模式,C库函数中支持的打开模式如下所示:
标志 含义
r、rb 以只读方式打开
w、wb 以只写方式打开,如果文件不存在,则创建文件,否则文件被截断。
a、ab 以追加方式打开。如果文件不存在,则创建该文件。
r+、r+b、rb+ 以读写方式打开
w+、w+b、wh+ 以读写方式打开。如果文件不存在,则创建新文件,否则文件被截断。
a+、a+b、ab+ 以读和追加方式打开。如果文件不存在。则创建新文件。
其中b用于区分二进制文件和文本文件,这一点在DOS、Windows系统中是有区分的,但Linux系统不区分二进制文件和文本文件。
3.2 读写
C库函数支持以字符、字符串等为单位,支持按照某种格式进行文件的读写,这一组函数为:
#include <stdio.h>
int fgetc(FILE *stream);
int fputc(int c , FILE *stream);
char *fgets(char *s , int n , FILE *stream);
int fputs(const char *s , FILE *stream);
int fprintf(FILE *stream , const char *format , ...);
int fscanf(FILE *stream , const char *format , ... );
size_t fread(void *ptr , size_t size , size_t n , FILE *stream);
size_t fwrite(const void *ptr , size_t size , size_t n FILE *stream);
fread()实现从stream中读取n个字段,每个字段为size个字节,并将读取的字段放放ptr所指的字符数组中,返回实际已读取的字段数。在读取的字段数小于num时,可能是在函数调用时出现错误,也可能是读到了文件的结尾。所以要通过调用feof()和ferror()来判断。
fwrite()实现从缓冲ptr所指的数组中把n个字段写到stream中,每个字段长为size个字节,返回实际写入的字段数。
另个,C库函数还提供了读写过程中的定位能力,这些函数包括:
int fgetpos(FILE *stream , fpos_t *pos);
int fsetpos(FILE *stream , const fpos_t *pos);
int fseek(FILE *stream , long offset , int whence);
3.3 关闭
利用C库函数关闭文件依然是很简单的操作,如下所示:
int fclose(FILE *stream);
例程:将上面的例子用C库函数来实现,如下:
#include <stdio.h>
#define LENGTH 20
int main()
{
FILE *fd;
char str[LENGTH];
fd = fopen("hello.txt","w+"); /*文件不存在,就创建*/
if(fd)
{
fputs("Hello World!",fd);
fclose(fd);
}
fd=fopen("hello.txt","r");
fgets(str,LENGTH,fd);
printf("%s ",str);
fclose(fd);
return 0;
}
4. 库函数和系统调用的区别在于系统调用能够让你直接访问Linux内核提供的丰富服务,比如基于文件描述符的I/O操作。可以把系统调用看作是内核的低级接口。另一方面,库调用处于Linux的编程接口中较高的层次。实际上,许多库函数都是用系统调用来实现的。库函数与系统调用之间的第二个关键区别在系统调用存在于内核空间,而大多数的库调用都是用模式的例程。
整合自: