错误处理
下面以具体代码来说明下:
编译运行:
可见perror的出错信息的格式是固定的,不是太灵活,下面再看下更灵活打印错误的方式:
编译运行:
通过这个程序,我们可以看到,用fprintf(关于它的使用,随后会进行详述)可以自定义错误输出格式,会比perror更加灵活。
说明:linux中有很多错误代码,如下:
都可以通过strerror来将错误码转换成错误文本,上面程序已经看到了,下面再来试验一下另外一个错误码:
什么是I/0
而对于linux来说,有两种类型的I/O:
文件描述符【这是新概念,需好好理解】
我们知道对于ANSI C库来讲,平常操纵文件是FILE文件指针来描述的, 如: FILE* fp;
实际上系统调用与ANSI C是一一对应的,只是描述不一样
文件描述符与文件指针相互转换
对于系统调用中的文件描述符与ANSI C标准库中的文件指针可以通过以下函数进行转换:
编译运行:
文件系统调用
open系统调用:
用man查看下:
运行:
对于其错误输出,还可以perror来输出,篇章开头已经介绍过了,如下:
运行:
下面要对代码进行封装了,对于程序出错都需要输出错误信息,并且让程序退出,所以,可以将上面这种错误输出的方式定义为宏,方便其之后调用,如下:
运行,看效果:
但是,上面的这种宏定义还不够专业,下面这种方式是linux内核中会经常用到的,体现了专业,呵呵:
从上面这种打开方式来看,如果文件不存在时,是不会去创建的,那有没有当文件不存时主动去创建呢,其中open的第二个参数就是用来设定打开方式的:
这次编译运行:
关于打开方式,有下列几种:
下面还有另外一种打开文件的函数:
对于这种打开方式,多了一个权限的参数,下面来看一下具体使用:
编译运行:
这里得对文件的权限进行说明一下,以便于理解这个权限参数:
理解了文件权限相关的知识,回到上面的程序来,我们在创建程序时,给定的文件权限是0666,也就是rw-rw-rw- ,但是从实际创建的文件来看,它的权限是:
这是为什么呢?这时需要解释一个新概念:umask
查看一下当前系统的umask是多少:
0666-0022=0644,就刚好是我们所看到的结果,但是注意,并非是真正意义上的相减运算,如果我们把权限改一下结果就不是这种公式推导出来的结果了:
先来按上面的公式来推算下创建文件的权限:0655-0022=0633,编译运行,见证事实啦:
可见,上面算权限的公式并非只是算术与umask相减,用简单的图来说明下umask的真正意义:
其实对于umask可以在程序中手动更改,我们可以将它设置为0,这样我们指定什么权限,最终文件创建就是什么权限了,如下:
编译运行:
目前我们以O_CREATE方式创建的文件,不管我们运行多少次,文件是否已经存在了,也是每次创建成功:
这时,如果想要不重复创建文件,则加上O_EXCL打开方式,再看下运行结果:
运行:
关于文件打开失败,有很多信息,这些全可以在open的帮助man里面找到,下面就对其中的某些错误进行一些解释:
那一个进程打开文件的最大数怎么查看呢?
一个系统打开文件的最大数又怎么查看呢?
另外,对于文件访问权限的指定除了直接输入数字之外,还能利用宏的方式创建,有如下权限组合:
实际上,查看open函数的帮助就有介绍:
下面以这种宏的方式来指定文件权限:
这时来查看下是否成功创建这种权限的文件:
close系统调用
这个比较简单:
creat系统调用【很少用到,可完全被open代替】
文件的read和write系统调用
下面以一个实现文件简单拷贝的示例(类似于cp命令,但是没cp命令强大),来对其文件的读写有个感性的认识:
#include#include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)int main(int argc/**参数总个数**/, char *argv[]/**具体参数值**/){ int infd;//读文件描述符 int outfd;//写文件描述符 if (argc != 3) { fprintf(stderr, "Usage %s src dest\n", argv[0]); exit(EXIT_FAILURE); } infd = open(argv[1], O_RDONLY); if (infd == -1) ERR_EXIT("open src error"); if ((outfd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC/**以清空的方式打开**/, 0644)) == -1) ERR_EXIT("open dest error"); char buf[1024]; int nread; //将读入的文件内容拷贝到新文件中 while ((nread = read(infd, buf, 1024)) > 0) { write(outfd, buf, nread); } close(infd); close(outfd); return 0;}复制代码
编译运行:
对于read和write,下面再来探讨一下它们之间细微的区别: 1、read读取过程中,有可能被某些信号给中断,这个在之后的信号中断时再学习。 2、read读指定字节的数据时,如果返回值大于0,表示已经从文件中将数据读到缓冲区当中了,但是write就不一定了,当我们写入时,如果返回大于0并非代表已经将缓冲区中的数据写入到磁盘当中了,仅仅只是表示数据缓冲区已经拷到内核缓冲区了,并未同步到磁盘上,如果想立即同步到磁盘中的话,可以利用下面这个函数既时同步:
实际上,我们也可以通过给open函数,设定一个flags,来达到同步的效果:
这时写文件时,就会一直阻塞到数据缓冲区写到物理磁盘。
文件的随机读写
说明:lseek对应于c语言fseek函数
#include#include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)int main(void){ int fd; fd = open("test.txt", O_RDONLY); if (fd == -1) ERR_EXIT("open error"); char buf[1024] = {0}; int ret = read(fd, buf, 5);//从文件中读取5个字节,这时文件指针就会偏移到5的位置 if (ret == -1) ERR_EXIT("read error"); printf("buf=%s\n", buf); ret = lseek(fd, 0, SEEK_CUR);//通过从当前文件的偏移值计算偏移,偏移量为0,就能计算文件当前的偏移量if (ret == -1) ERR_EXIT("lseek"); printf("current offset=%d\n", ret); return 0; }复制代码
首先我们创建一个test.txt,以便进行程序测试:
编译运行:
利用lseek产生空洞文件
编译运行:
我们来查看一下它的内容:
这时,采用另外一个命令:
这时,我们查看一下其大小:
这时,为了说明空洞内容是没有存放在磁盘中的,这时,我们将偏移量加大:
编译运行,来查看下内容大小:
总结:
一个文件的大小不等于一个文件在磁盘所占用的空间,下面再来简单说明下:
目录访问
说明:struct dirent目录信息结构体的核心结构如下:
下面以一个具体示例来使用上面的这些目录函数【功能跟ls命令类似,简易版】:
编译运行:
如果想过滤掉隐藏文件,则可以修下代码如下:
再次运行:
- mkdir
- rmdir
- chmod和fchmod
- chown和fchown
stat:功能:读取文件元数据
stat结构体的结构如下:
struct stat { dev_t st_dev; /* ID of device containing file文件保存在磁盘上的设备号,包含主设备号和次设备号,它是16位的整数,高八位为主设备号,低八位为次设备号 */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection文件的权限信息 */ nlink_t st_nlink; /* number of hard links 文件的硬连接数*/ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) 如果文件是设备文件,所对应的设备ID*/ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O 系统当中每个块的大小 */ blkcnt_t st_blocks; /* number of 512B blocks allocated 块数目 */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification 最后的修改时间*/ time_t st_ctime; /* time of last status change 最后状态改变的时间,比如说改变了文件权限,并未改变文件的内容*/};复制代码
#include#include #include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)#define MAJOR(a) (int)((unsigned short)a >> 8)//获取高八位数据,也就是主设备号#define MINOR(a) (int)((unsigned short)a & 0xFF)//获取低八位数据,也就是次设备号int main(int argc, char *argv[]){ if (argc != 2) { fprintf(stderr, "Usage %s file\n", argv[0]); exit(EXIT_FAILURE); } struct stat sbuf; printf("Filename:%s\n", argv[1]);//打印文件名 if (stat(argv[1], &sbuf) == -1) ERR_EXIT("stat error"); printf("File number:major %d,minor %d inode %d\n", MAJOR(sbuf.st_dev)/**文件主设备号**/, MINOR(sbuf.st_dev)/**文件次设备号**/, sbuf.st_ino/**inode数**/); return 0;}复制代码
编译:
实际上上面的数据都可由系统命令来进行查看,如下:
总结:主设备号用来区分不同的设备,它决定了用什么样的驱动程序来访问设备;次设备号用来区分同设备中的不同分区。
下面我们继续来查看文件信息,先来打印下文件类型,关于这个数据,它是存放到stat结构体中的mode_t字段中:
先来查看下man帮助:
#include#include #include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)#define MAJOR(a) (int)((unsigned short)a >> 8)#define MINOR(a) (int)((unsigned short)a & 0xFF)int filetype(struct stat *buf);//获取文件类型函数定义int main(int argc, char *argv[]){ if (argc != 2) { fprintf(stderr, "Usage %s file\n", argv[0]); exit(EXIT_FAILURE); } struct stat sbuf; printf("Filename:%s\n", argv[1]); if (lstat(argv[1], &sbuf) == -1) ERR_EXIT("stat error"); printf("File number:major %d,minor %d inode %d\n", MAJOR(sbuf.st_dev), MINOR(sbuf.st_dev), (int)sbuf.st_ino); if (filetype(&sbuf)) { //如果文件类型是设备文件,还可以获取设备更详细的信息:主设备号、次设备号 printf("Device number:major %d,minor %d\n", MAJOR(sbuf.st_rdev), MINOR(sbuf.st_rdev)); } return 0;}int filetype(struct stat *buf)//如果是设备文件,则返回1,否则返回0{ int flag = 0; printf("Filetype:"); mode_t mode; mode = buf->st_mode; switch (mode & S_IFMT) { case S_IFSOCK: printf("socket\n"); break; case S_IFLNK: printf("symbolic link\n"); break; case S_IFREG: printf("regular file\n"); break; case S_IFBLK: printf("block device\n"); flag = 1; break; case S_IFDIR: printf("directory\n"); break; case S_IFCHR: printf("character device\n"); flag = 1; break; case S_IFIFO: printf("FIFO\n"); break; default: printf("unknown file type\n"); break; } return flag;}复制代码
编译运行:
下面来打印文件的权限信息:
#include#include #include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)#define MAJOR(a) (int)((unsigned short)a >> 8)#define MINOR(a) (int)((unsigned short)a & 0xFF)int filetype(struct stat *buf);void fileperm(struct stat *buf, char *perm);//查看文件权限,以rwx进行格式化int main(int argc, char *argv[]){ if (argc != 2) { fprintf(stderr, "Usage %s file\n", argv[0]); exit(EXIT_FAILURE); } struct stat sbuf; printf("Filename:%s\n", argv[1]); if (stat(argv[1], &sbuf) == -1) ERR_EXIT("stat error"); printf("File number:major %d,minor %d inode %d\n", MAJOR(sbuf.st_dev), MINOR(sbuf.st_dev), (int)sbuf.st_ino); if (filetype(&sbuf)) { printf("Device number:major %d,minor %d\n", MAJOR(sbuf.st_rdev), MINOR(sbuf.st_rdev)); } //打印文件的权限信息 char perm[11] = {0}; fileperm(&sbuf, perm); printf("File permission bits=%o %s\n", sbuf.st_mode & 07777, perm); return 0;}int filetype(struct stat *buf){ int flag = 0; printf("Filetype:"); mode_t mode; mode = buf->st_mode; switch (mode & S_IFMT) { case S_IFSOCK: printf("socket\n"); break; case S_IFLNK: printf("symbolic link\n"); break; case S_IFREG: printf("regular file\n"); break; case S_IFBLK: printf("block device\n"); flag = 1; break; case S_IFDIR: printf("directory\n"); break; case S_IFCHR: printf("character device\n"); flag = 1; break; case S_IFIFO: printf("FIFO\n"); break; default: printf("unknown file type\n"); break; } return flag;}void fileperm(struct stat *buf, char *perm){ strcpy(perm, "----------"); perm[0] = '?'; mode_t mode; mode = buf->st_mode; switch (mode & S_IFMT)//确定第一位文件类型 { case S_IFSOCK: perm[0] = 's'; break; case S_IFLNK: perm[0] = 'l'; break; case S_IFREG: perm[0] = '-'; break; case S_IFBLK: perm[0] = 'b'; break; case S_IFDIR: perm[0] = 'd'; break; case S_IFCHR: perm[0] = 'c'; break; case S_IFIFO: perm[0] = 'p'; break; } if (mode & S_IRUSR)//与用户读权限进行与操作之后,如果为真,则代表有这个权限 perm[1] = 'r'; if (mode & S_IWUSR) perm[2] = 'w'; if (mode & S_IXUSR) perm[3] = 'x'; if (mode & S_IRGRP) perm[4] = 'r'; if (mode & S_IWGRP) perm[5] = 'w'; if (mode & S_IXGRP) perm[6] = 'x'; if (mode & S_IROTH) perm[7] = 'r'; if (mode & S_IWOTH) perm[8] = 'w'; if (mode & S_IXOTH) perm[9] = 'x'; perm[10] = '\0';}复制代码
编译运行:
利用上面所学的东西,我们就可以实现自己的ls -l的功能了,在实现这个功能之时,还有几个知识点需介绍一下:
先看一下ls -l会显示什么信息:
①文件权限,这个上面已经实现过了,所以不成问题
②连接数,对应的是stat->nlink_t,所以不成问题
③用户名,我们知道stat->uid_t,可以根据用户ID获得用户名,通过如下函数:
④组名,我们知道stat->gid_t,可以根据组ID获得组名,通过如下函数:
⑤文件大小,可以通过stat->st_size来获得
⑥文件修改时间,可以通过stat->st_mtime来获得
⑦文件名,这个肯定可以获取。
但是,对于符号链接文件,还需要做一个特珠的处理,如下:
那对于符号链接文件,怎么才能获取它所指向的链接文件呢?可以通过如下函数获取:
所以说,我们完全有能力实现跟ls -l系统命令一样的功能:
注意:
对于符号链接文件,我们查看其文件信息时,应该是lstat函数,而不是stat 对于目前我们实现查看的程序,是用的stat函数,那查看我们新创建的a链接文件的信息是什么呢?
这时,我们将state改为lstate,再次查看下:
编译运行:
查看一下lsate函数的说明:
文件共享
在linux系统调用中,是通过文件描述符来访问文件的,文件描述符是一个非负的整数,这是站在用户的观点来看的,实际上在linux内核上是有一定的数据结构来表示文件描述符的,下面就从三方面来看图分析一下内核中是怎么来表示打开的文件的:
一个进程打开两个文件内核数据结构:
所以,由于文件描述符0、1、2被占用了,则我们用户打开的文件描述符只能从第3开始了:
如图上所示,对于内核,当打开一个文件时,会有一张表格来记录文件的状态,回顾一下,当我们在读取文件内容的时候,会自动的从当前文件的偏移位置去读取下一个数据,原因就在于这个偏移量就保存在文件表当中的:
而每当我们打开一个文件时,内核就会为文件分配一个文件表,里面有不同的项,其中当前文件偏移量就是一个文件表项 回忆一下,我们打开一个文件,可以以读、写、追加、同步、非阻塞(这个之后会学到)等方式打开,用来描述它的就是另一个文件表项:文件状态标志
另外图中还有一个文件引用计数,它是用来描述一个文件被多少个文件描述符指向了(这个在下面的复制文件描述符中就可以体会到了):
另外,还有一个文件表项,它是v节点指针,它指向了v节点表,如图所示:
其中v节点表中,存放了两项很重要的信息,一个是v节点信息,一个是i节点信息: v节点信息:我们上节学习的stat函数获得文件信息返回的状态信息就全保存在v节点信息里。
i节点信息:当我们打开一个文件时,会将文件系统当中的i结点数据拷贝到v节点表中的i节点信息所存放的位置,比如说:
一个进程两次打开同一个文件内核数据结构:
#include#include #include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)int main(int argc, char *argv[]){ int fd1; int fd2; char buf1[1024] = {0}; char buf2[1024] = {0}; fd1 = open("test.txt", O_RDONLY);//以只读的方式打开文件 if (fd1 == -1) ERR_EXIT("open error"); read(fd1, buf1, 5); printf("buf1=%s\n", buf1); fd2 = open("test.txt", O_RDWR);//以读写的方式打开文件 if (fd2 == -1) ERR_EXIT("open error"); read(fd2, buf2, 5); printf("buf2=%s\n", buf2); close(fd1); close(fd2); return 0;}复制代码
先新建一个test.txt,里面输点测试内容:
编译运行:
也就是各个描述符有各自的偏移量,当buf1输出ABCDE时,如果第二个描述符共享偏移量的话,应该buf2输出FGhel,但是buf2输出的也是ABCDE,也就说明了各个文件描述符有不同的文件表项。
#include#include #include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)int main(int argc, char *argv[]){ int fd1; int fd2; char buf1[1024] = {0}; char buf2[1024] = {0}; fd1 = open("test.txt", O_RDONLY); if (fd1 == -1) ERR_EXIT("open error"); read(fd1, buf1, 5); printf("buf1=%s\n", buf1); fd2 = open("test.txt", O_RDWR); if (fd2 == -1) ERR_EXIT("open error"); read(fd2, buf2, 5); printf("buf2=%s\n", buf2); write(fd2, "AAAAA", 5); memset(buf1, 0, sizeof(buf1)); read(fd1, buf1, 5); printf("buf1=%s\n", buf1); close(fd1); close(fd2); return 0;}复制代码
编译运行:
这结果为什么是它呢?下面来分析下:
write(fd2, "AAAAA", 5);
这时我们先来查看下test.txt的内容:
由于它会改变v节点表中的i节点信息所指向磁盘中的数据,而两个文件描述符的v节点表是共享的,而fd1此时的偏移量为5:
总结:每打开一个文件描述符,就有一个对应的文件表项描述,而如果打开的是同一个文件,v节点表是共享的
两个进程打开同一个文件内核数据结构:
说明:不同的进程可以打开同一个文件,但是每个进程的文件描述符对应一个独立的文件表项,而最终共享v节点表。 总结:文件描述符跟文件不是一一对应的,文件描述符可以有多个,但是文件可以只有一个。
复制文件描述符
其中复制文件描述符,可以执行dup命令,注意,这时它会从0开始找出有空闲的文件描述符,如图,0、1、2是已经默认被系统给占用了,这时,执行dup之后,就会找到空闲的fd 4文件描述符,将它也指向同一个文件表,如图:
下面,以一段程序来说明一下输出重定向的原理,先复习一下什么是输出重定向:
下面,以具体程序来说明它,利用的就是复制文件描述符的知识:
先本地建一个空的test2.txt文件:
分析一下这个程序:
close(1)的作用,就是为了让输出到屏幕的文件描述符成为空闲的,然后dup时,会从0开始找空闲文件描述符,发现1是空闲的,则这时它的内存模型就变成这样了:
所以,清楚了它之后,对于ls > aa这样的输出重定向的功能,就比较容易实现了。
另外,对于复制文件描述符有三种方法:
对于dup2,理解它,我们可以将上面复制文件描述符的程序用dup2代替dup,如下:
编译运行:
另外第三种复制文件描述符的方法,是通过fcntl函数,它稍复杂一些,这个会在下节详细进行分析,先看一下man帮助:
其中第三个参数,可以决定复制文件描述符时,从第几个描述符开始搜索空闲,利用dup实现复制文件描述符时都是从0开始搜索的。
fcntl常用操作
我们将上节当中用dup或dup2实现复制文件描述符改用fcntl,程序如下:
先将test2.txt的内容清空,以便进行测试,编译运行:
通过man来查看下它的说明:
先回顾一下都有哪些状态标志:
也就是说,通过这个命令,能更改文件状态标志,说来有些难理解,下面以实例代码来进行一一说明:
编译运行:
当输入内容时,read才读取完,并打印出输入的内容:
这时本来的文件状态,但是,可以fcntl函数,来改变这种阻塞状态为非阻塞状态,如下:
#include#include #include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)int main(int argc, char *argv[]){ char buf[1024] = {0}; int ret; int flags; flags = fcntl(0, F_GETFL, 0);//通过F_GETFL来获得标准输入的状态 if (flags == -1) ERR_EXIT("fcntl get flag error"); ret = fcntl(0, F_SETFL, flags | O_NONBLOCK);//通过F_SETFL来改变文件的状态为非阻塞0_NONBLOCK,但是为了保留其它状态,所以设置之前需获得状态,再进行与操作 if (ret == -1) ERR_EXIT("fcntl set flag error"); ret = read(0, buf, 1024); if (ret == -1) ERR_EXIT("read error"); printf("buf=%s\n", buf); return 0;}复制代码
编译运行:
实际上,对于上面这个错误,对应的错误代码是:
【注意:在设置状态时,一定得先用F_GETFL获取状态,然后再去用F_SETFL去设置,因为我们只想设置非阻塞的状态,对于其它状态如:写状态,读状态等想保留】
关于上面这段设置状态的代码,可以进行封装,以便进行复用,如下:
#include#include #include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)void set_flag(int fd, int flags);//设置文件状态标志函数声明int main(int argc, char *argv[]){ char buf[1024] = {0}; int ret; set_flag(0, O_NONBLOCK);//这时,经过代码封装之后,代码就显得比较干净了 ret = read(0, buf, 1024); if (ret == -1) ERR_EXIT("read error"); printf("buf=%s\n", buf); return 0;}//设置文件状态标志void set_flag(int fd, int flags){ int val; val = fcntl(fd, F_GETFL, 0); if (val == -1) ERR_EXIT("fcntl get flag error"); val |= flags; if (fcntl(fd, F_SETFL, val) < 0) ERR_EXIT("fcntl set flag error");}复制代码
另外,我们还可以封装一个清除文件状态标志的函数,跟设置很类似,如下:
#include#include #include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)void set_flag(int fd, int flags);void clr_flag(int fd, int flags);//清除文件状态标志函数声明int main(int argc, char *argv[]){ char buf[1024] = {0}; int ret; set_flag(0, O_NONBLOCK); clr_flag(0, O_NONBLOCK);//清除了非阻塞的状态标记,也就是等于最终还是阻塞状态 ret = read(0, buf, 1024); if (ret == -1) ERR_EXIT("read error"); printf("buf=%s\n", buf); return 0;}void set_flag(int fd, int flags){ int val; val = fcntl(fd, F_GETFL, 0); if (val == -1) ERR_EXIT("fcntl get flag error"); val |= flags; if (fcntl(fd, F_SETFL, val) < 0) ERR_EXIT("fcntl set flag error");}//清除文件状态标志void clr_flag(int fd, int flags){ int val; val = fcntl(fd, F_GETFL, 0); if (val == -1) ERR_EXIT("fcntl get flag error"); val &= ~flags; if (fcntl(fd, F_SETFL, val) < 0) ERR_EXIT("fcntl set flag error");}复制代码
编译运行:
先来解释一下结构体字段:
#include#include #include #include #include #include #include #include #include #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)int main(int argc, char *argv[]){ int fd; fd = open("test2.txt", O_CREAT | O_RDWR | O_TRUNC, 0644); if (fd == -1) ERR_EXIT("open error"); struct flock lock; memset(&lock, 0, sizeof(lock)); lock.l_type = F_WRLCK;//加上排它锁 lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(fd, F_SETLK, &lock) == 0) { printf("lock success\n"); printf("press any key to unlock\n"); getchar(); lock.l_type = F_UNLCK;//释放锁 if (fcntl(fd, F_SETLK, &lock) == 0) printf("unlock success\n"); else ERR_EXIT("unlock fail"); } else ERR_EXIT("lock fail"); return 0;}复制代码
编译运行:
实际上,上面出错的错误代码也是EAGAIN,查看一下fcntl函数:
【注意:如果要给文件加读锁,则文件需要有读的权限;如果要给文件写写锁,则文件也需要有写的权限】
另外,设置文件锁,还有另外一种操作命令:F_SETLKW,它跟F_SETLK有啥区别呢?且看下代码:
编译运行:
【总结:F_SETLK设置锁时,如果进程没有成功设置上锁,则会立马给出错误提示;F_SETLKW设置锁时,如果进程没有成功设置上锁,会阻塞,类似于线程的同步一样,当对方的锁释放时,则才可以对文件进行上锁】
另外,如果想获得阻塞进程的ID,可以用F_GETLK,它会将id保存在flock结构体中的l_pid当中.