【Linux C | 文件I/O】fcntl函数详解 | 设置描述符非阻塞、文件(记录)锁

2024-06-04 6150阅读

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀

🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭

🤣本文内容🤣:🍭介绍🍭

😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭

本文未经允许,不得转发!!!

目录

  • 🎄一、fcntl 函数介绍
  • 🎄二、复制文件描述符(F_DUPFD、F_DUPFD_CLOEXEC)
    • ✨2.1 F_DUPFD(int)
    • ✨2.2 F_DUPFD_CLOEXEC(int)
    • 🎄三、获取/设置文件描述符标志(F_GETFD、F_SETFD)
    • 🎄四、获取/设置文件状态标志(F_GETFL、F_SETFL)
    • 🎄五、获取/设置记录锁(F_GETLK、F_SETLK、F_SETLKW)
    • 🎄六、总结

      【Linux C | 文件I/O】fcntl函数详解 设置描述符非阻塞、文件(记录)锁 第1张

      🎄一、fcntl 函数介绍

      函数原型:

      #include 
      #include 
      int fcntl(int fd, int cmd, ... /* arg */ );
      // arg表示可变参数,由cmd决定
      

      fcntl()对打开的文件描述符fd执行下面描述的操作之一。操作由cmd决定。

      fcntl()的第三个参数是可选。是否需要此参数由cmd决定。所需的参数类型在每个cmd名称后面的括号中指示(在大多数情况下,所需的类型是int,我们使用名称arg来标识参数),如果不需要参数,则指定void。

      以下某些操作仅在特定的Linux内核版本之后才受支持。检查主机内核是否支持特定操作的首选方法是使用所需的cmd值调用fcntl(),然后使用EINVAL测试调用是否失败,这表明内核无法识别该值。

      本文主要介绍下面4个功能:

      • 1、复制文件描述符(F_DUPFD、F_DUPFD_CLOEXEC);
      • 2、获取/设置文件描述符标志(F_GETFD、F_SETFD);
      • 3、获取/设置文件状态标志(F_GETFL、F_SETFL);
      • 4、获取/设置记录锁(F_GETLK、F_SETLK、F_SETLKW);

        【Linux C | 文件I/O】fcntl函数详解 设置描述符非阻塞、文件(记录)锁 第2张

        🎄二、复制文件描述符(F_DUPFD、F_DUPFD_CLOEXEC)

        ✨2.1 F_DUPFD(int)

        F_DUPFD(int) 表示使用 F_DUPFD 作为cmd时,第三个参数需要传入int型数据。

        cmd为F_DUPFD表示复制文件描述符fd。调用成功会返回新的描述符。新描述符使用大于或等于arg参数的编号最低的可用文件描述符复制文件描述符fd。新描述符与f似共享同一文件表项。但是,新描述符有它自己的一套文件描述符标志,其FD_CLOEXEC文件描述符标志被清除〈这表示该描述符在 exec 时仍保持打开状态)。

        // fcntl_F_DUPFD.c
        #include 
        #include 
        #include 
        #include 
        int main()
        {
        	int fd = open("./fcntl_F_DUPFD", O_RDWR | O_CREAT | O_TRUNC, 0775);
        	int fcntlFd = fcntl(fd, F_DUPFD, 0); // 指定从 0 开始分配最小的可用描述符作为新描述符
        	int dupFd = dup(fd); // 等效于 fcntl(fd, F_DUPFD, 0);
        	
        	close(fd);
        	close(fcntlFd);
        	close(dupFd);
        	return 0;
        }
        

        ✨2.2 F_DUPFD_CLOEXEC(int)

        F_DUPFD_CLOEXEC(int) 表示使用 F_DUPFD_CLOEXEC 作为cmd时,第三个参数需要传入int型数据。

        cmd为F_DUPFD_CLOEXEC的功能与F_DUPFD类似,区别在于F_DUPFD_CLOEXEC在复制的同时会设置文件描述符标志FD_CLOEXEC,表示在执行exec系列函数后,该描述符会关闭。

        看例子:F_GETFD和dup函数都会清除新描述符的FD_CLOEXEC标志,F_DUPFD_CLOEXEC会复制并设置FD_CLOEXEC标志。

        #include 
        #include 
        #include 
        #include 
        int main()
        {
        	int fd = open("./fcntl_F_DUPFD_CLOEXEC", O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0775);
        	int fcntlFd = fcntl(fd, F_DUPFD, 0);
        	int fcntlCloFd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
        	int dupFd = dup(fd);
        	
        	int fdFlag = fcntl(fd, F_GETFD, 0);
        	int fcntlFdFlag = fcntl(fcntlFd, F_GETFD, 0);
        	int fcntlCloFdFlag = fcntl(fcntlCloFd, F_GETFD, 0);
        	int dupFdFlag = fcntl(dupFd, F_GETFD, 0);
        	
        	// 结果是:fdFlag=1, fcntlFdFlag=0, fcntlCloFdFlag=1, dupFdFlag=0
        	printf("fdFlag=%d, fcntlFdFlag=%d, fcntlCloFdFlag=%d, dupFdFlag=%d\n",
        		fdFlag,fcntlFdFlag,fcntlCloFdFlag,dupFdFlag);
        	
        	close(fd);
        	close(fcntlFd);
        	close(fcntlCloFd);
        	close(dupFd);
        	return 0;
        }
        

        运行结果:

        【Linux C | 文件I/O】fcntl函数详解 设置描述符非阻塞、文件(记录)锁 第3张

        【Linux C | 文件I/O】fcntl函数详解 设置描述符非阻塞、文件(记录)锁 第4张

        🎄三、获取/设置文件描述符标志(F_GETFD、F_SETFD)

        当前只定义了一个文件描述符标志FD_CLOEXEC。用来表示该描述符在执行完fork+exec系列函数创建子进程时会自动关闭,以防止它们被传递给子进程。为什么要这样做呢?

        因为当一个进程调用exec系列函数(比如execve)来创建子进程时,所有打开的文件描述符都会被传递给子进程。如果文件描述符没有设置FD_CLOEXEC标志,这些文件将保持打开状态并继续对子进程可见。这可能导致潜在的安全风险或者意外行为。

        F_GETFD(void) :表示使用 F_GETFD 作为cmd时,不需要传入第三个参数。

        功能:获取文件描述符标志。

        返回值:

        • 成功返回文件描述符标志
        • 失败返回 -1.

          F_SETFD(int):表示使用 F_SETFD 作为cmd时,传入第三个参数是int型的。

          功能:设置文件描述符标志,第三个参数传入新的标志值。

          返回值:

          • 成功返回 0
          • 失败返回 -1.

            文件描述符的FD_CLOEXEC标志可以通过三个方法得到:

            • 1、调用open函数是,指定 O_CLOEXEC
            • 2、通过fcntl函数使用F_DUPFD_CLOEXEC复制文件描述符,新的描述符就是FD_CLOEXEC
            • 3、通过fcntl函数使用F_SETFD直接设置FD_CLOEXEC。

              看例子:

              #include 
              #include 
              #include 
              #include 
              int main()
              {
              	int fd = open("./fcntl_F_GETFD", O_RDWR | O_CREAT | O_TRUNC, 0775);
              	int fdCloExec = open("./fcntl_F_GETFD2", O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0775);
              	int fdCloExecDup = fcntl(fd, F_DUPFD_CLOEXEC, 0);
              	int fdSetFd = dup(fd);
              	fcntl(fdSetFd, F_SETFD, FD_CLOEXEC);
              	
              	int flagFd = fcntl(fd, F_GETFD);
              	int flagFdCloExec = fcntl(fdCloExec, F_GETFD);
              	int flagFdCloExecDup = fcntl(fdCloExecDup, F_GETFD);
              	int flagFdSetFd = fcntl(fdSetFd, F_GETFD);
              	// 打印结果:flagFd=0, flagFdCloExec=1, flagFdCloExecDup=1 flagFdSetFd=1
              	printf("flagFd=%d, flagFdCloExec=%d, flagFdCloExecDup=%d flagFdSetFd=%d\n", 
              		flagFd,flagFdCloExec,flagFdCloExecDup,flagFdSetFd);
              	close(fd);
              	close(fdCloExec);
              	close(fdCloExecDup);
              	close(fdSetFd);
              	return 0;
              }
              

              【Linux C | 文件I/O】fcntl函数详解 设置描述符非阻塞、文件(记录)锁 第5张

              🎄四、获取/设置文件状态标志(F_GETFL、F_SETFL)

              文件状态标志如下表:

              文件状态标志说明十六进制值
              O_RDONLY只读打开0x0
              O_WRONLY只写打开0x1
              O_RDWR读、写打开0x2
              O_APPEND追加写0x400
              O_NONBLOCK非阻塞模式0x800
              O_SYNC等待写完成(数据和属性)0x
              O_DSYNC等待写完成(仅数据)
              O_RSYNC同步读和写
              O_FSYNC等待写完成
              O_ASYNC异步IO0x2000

              F_GETFL(void) :表示使用 F_GETFL 作为cmd时,不需要传入第三个参数。

              功能:获取文件状态标志。

              返回值:

              • 成功返回文件状态标志
              • 失败返回 -1.

                访问方式标志:O_RDONLY 、O_WRONLY、O_RDWR。这3个值是互斥的,因此首先必须用屏蔽O_ACCMODE取得访问方式位,然后将结果与这3个值中的每一个相比较。

                F_SETFL(int):表示使用 F_SETFL 作为cmd时,传入第三个参数是int型的。

                功能:设置文件状态标志,第三个参数传入新的文件状态标志值。

                返回值:

                • 成功返回 0
                • 失败返回 -1.

                  在Linux上,只能设置这5个文件状态标志:O_APPEND、 O_ASYNC、 O_DIRECT、 O_NOATIME、O_NONBLOCK,其中最常用的是将文件描述符设置成非阻塞(O_NONBLOCK),特别是在网络编程中很常见。

                  看例子,设置文件状态标志在日常使用中,就用来设置非阻塞,其他的可以先不关注。

                  #include 
                  #include 
                  #include 
                  #include 
                  int main()
                  {
                  	int fd = open("./fcntl_F_GETFL", O_RDWR | O_CREAT | O_TRUNC | O_NONBLOCK, 0775);
                  	int flag = fcntl(fd, F_GETFL);
                  	if(flag
                  	...
                  	short l_type;//锁的类型:F_RDLCK(读锁)、F_WRLCK(写锁)、F_UNLCK(解锁)
                  	short l_whence;//偏移的起点:SEEK_SET、SEEK_CUR、SEEK_END 
                  	off_t l_start; //锁区偏移,从l_whence
                  	off_t l_len;  //锁区长度(字节)
                  	pid_t l_pid; //阻塞我们加锁的进程ID
                  	...
                  }
                  
                  	if (pLock == NULL)
                  		return -1;
                  	pLock-l_type=type;
                  	pLock->l_whence=whence;
                  	pLock->l_start=start;
                  	pLock->l_len=len;
                  	pLock->l_pid = -1;
                  	return 0;
                  }
                  int main()
                  {
                  	int fd = open("./fcntl_lock", O_RDWR | O_CREAT | O_TRUNC, 0664);
                  	
                  	int i=0;
                  	for(i=0; i
                  		char num = i+1;
                  		write(fd, &num, 1);
                  	}
                  	
                  	// 加读锁
                  	struct flock rlock;
                  	makeFlock(&rlock, F_RDLCK, SEEK_SET, 11, 10); // 读锁,锁第11~20个字节
                  	int ret = fcntl(fd, F_SETLK, &rlock);
                  	if(ret
                  		return -1;
                  	}
                  	
                  	// 读取数据
                  	printf("reading 11~20 byte:\n");
                  	lseek(fd, 10, SEEK_SET);
                  	for(i=0; i
                  		char num;
                  		read(fd, &num, 1);
                  		printf("reading byte: %d\n",num);
                  		sleep(1);
                  	}
                  	sleep(30);
                  	
                  	// 释放锁
                  	rlock.l_type = F_UNLCK;
                      ret = fcntl(fd, F_SETLK, &rlock);
                      if(ret == -1)
                      {
                          perror("rlock release failed");
                          return -1;
                      }
                      sleep(30);
                      close(fd);
                  	return 0;
                  }
                  
                  	if (pLock == NULL)
                  		return -1;
                  	pLock-l_type=type;
                  	pLock-l_whence=whence;
                  	pLock-l_start=start;
                  	pLock->l_len=len;
                  	pLock->l_pid = -1;
                  	return 0;
                  }
                  int main()
                  {
                  	int i=0;
                  	int fd = open("./fcntl_lock", O_RDWR);	
                  	// 加读锁,与进程1重叠,仍然可以读
                  	struct flock rlock;
                  	makeFlock(&rlock, F_RDLCK, SEEK_SET, 6, 10); // 读锁,锁第6~15个字节,与进程1的11~20有重叠
                  	int ret = fcntl(fd, F_SETLK, &rlock);
                  	if(ret == -1)
                  	{
                  		return -1;
                  	}
                  	// 读取数据
                  	printf("reading 6~15 byte:\n");
                  	lseek(fd, 5, SEEK_SET);
                  	for(i=0; i
                  		char num;
                  		read(fd, &num, 1);
                  		printf("reading byte: %d\n",num);
                  	}
                  	// 释放读锁
                  	rlock.l_type = F_UNLCK;
                      ret = fcntl(fd, F_SETLK, &rlock);
                      if(ret == -1)
                      {
                          perror("rlock release failed");
                          return -1;
                      }	
                  	// 加写锁,此时会失败,因为进程1加了读锁没释放
                  	struct flock wlock;
                  	makeFlock(&wlock, F_WRLCK, SEEK_SET, 6, 10); // 写锁,锁第6~15个字节,与进程1的11~20有重叠
                      ret = fcntl(fd, F_SETLK, &wlock);
                      if(ret == -1)
                      {
                          perror("rlock release failed");
                      }
                      sleep(30);
                      close(fd);
                  	return 0;
                  }
                  

    免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

    目录[+]