博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux - IO
阅读量:6267 次
发布时间:2019-06-22

本文共 16814 字,大约阅读时间需要 56 分钟。

错误处理

下面以具体代码来说明下:

编译运行:

可见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当中.

转载于:https://juejin.im/post/5ce769e45188255b9921571c

你可能感兴趣的文章
推断Windows版本号新方法
查看>>
2017-4-18 ADO.NET
查看>>
RSuite 一个基于 React.js 的 Web 组件库
查看>>
技术博客网址收藏
查看>>
python 金融分析学习
查看>>
授人以渔不如授人以鱼
查看>>
matlab练习程序(图像Haar小波变换)
查看>>
【Java】从域名得到ip
查看>>
Mysql索引会失效的几种情况分析
查看>>
LVM逻辑卷
查看>>
zoj3591 Nim(Nim博弈)
查看>>
canvas绘图
查看>>
poj - 3039 Margaritas on the River Walk
查看>>
bootstrap(5)关于导航
查看>>
Aptana插件在eclipse中安装
查看>>
jQuery-数据管理-删除事件
查看>>
下载器简单实例
查看>>
java实现分页工具类(JDBC)
查看>>
欧几里德算法与扩展欧几里德算法
查看>>
Tinkoff Internship Warmup Round 2018 and Codeforces Round #475 (Div. 2)
查看>>