open函数中O_CLOEXEC标志的开关

man open里有这么一个flag:

O_CLOEXEC (Since Linux 2.6.23)
              Enable the close-on-exec flag for the new file descriptor.  Specifying this flag permits a program to  avoid  additional  fcntl(2)  F_SETFD
              operations  to  set the FD_CLOEXEC flag.  Additionally, use of this flag is essential in some multithreaded programs since using a separate fcntl(2) F_SETFD operation to set the FD_CLOEXEC flag does not suffice to avoid race conditions where one
thread opens a file descriptor at the same time as another thread does a fork(2) plus execve(2).

  意思就是新的内核里的这个选项是把fcntl的这个设置放在open里原子操作,以免在多线程程序里有可能会出现fcntl在设置的同时其它线程在fork+execve,虽然在线程里fork比较罕见.这个选项的意思就是子进程默认是继承父进程打开的所有fd,如果句柄加入了这个设置,在execve替换进程时就会关闭设置这个选项的所有fd.当调用exec()函数成功后,文件描述符会自动关闭。在以往的内核版本(2.6.23以前)中,需要调用
fcntl(fd, F_SETFD, FD_CLOEXEC) 来设置这个属性。而
新版本(2.6.23开始)中,可以在调用open函数的时候,通过 flags 参数设置 CLOEXEC 功能,如 open(filename, O_CLOEXEC)。

      虽然新版本支持在open时设置CLOEXEC,但是在编译的时候还是会提示错误 - error: ‘O_CLOEXEC’ undeclared (first use in this function)。原来这个新功能要求我们手动去打开,需要设置一个宏(_GNU_SOURCE)。可通过以下两种方法来设置这个宏以打开新功能:

1. 在源代码中加入 #define _GNU_SOURCE
2. 在编译参数中加入 -D_GNU_SOURCE

#include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <pthread.h>
 
 int flag=0;
 void* threadFunc(void* arg)
 {
 #ifdef _CLOEXEC_
     int fd = open("mm.log", O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, 0644);
 #else
     int fd = open("mm.log", O_CREAT|O_WRONLY|O_TRUNC, 0644);
 #endif
     printf("fd for mm.log is : %d\n", fd);
     if(fd<0) { perror("open mm.log failed"); exit(-1);}
 
     char s[32]="";
     sprintf(s, "%u", getpid());
     write(fd, s, strlen(s));
     flag=1;
     sleep(1000);
 }
 
 int main(int argc, char* argv[])
 {
     pthread_t thd;
     pthread_create(&thd, NULL, threadFunc, NULL);
 
     while(!flag);
 
     pid_t pid;
     pid=fork();
     if(pid<0) { perror("fork failed"); exit(-1);}
     else if(pid==0)
     {
         //child
         if(execl("/bin/sleep", "sleep", "100")== -1) { perror("exit failed"); exit(-1);}
 
         _exit(0);
     }
     printf("child pid: %u\n", pid);
     exit(0);
 }

使用两个选项编译两个程序:

[libgcc@Heineken ~/cpp]$ gcc -U_CLOEXEC_ -o nocloexec o_cloexec.c -lpthread 
[libgcc@Heineken ~/cpp]$ gcc -D_CLOEXEC_ -o cloexec o_cloexec.c -lpthread  
[libgcc@Heineken ~/cpp]$ ./nocloexec 
fd for mm.log is : 3
child pid: 2201
[libgcc@Heineken ~/cpp]$ ps -ef|grep 220[1]
libgcc    2201     1  0 03:23 pts/0    00:00:00 sleep 100
[libgcc@Heineken ~/cpp]$ lsof -p 2201
COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
sleep   2201 libgcc  cwd    DIR  253,0     4096 319493 /home/libgcc/cpp
sleep   2201 libgcc  rtd    DIR  253,0     4096      2 /
sleep   2201 libgcc  txt    REG  253,0    25688 262402 /bin/sleep
sleep   2201 libgcc  mem    REG  253,0   151500 677607 /lib/ld-2.12.90.so
sleep   2201 libgcc  mem    REG  253,0  1889628 677608 /lib/libc-2.12.90.so
sleep   2201 libgcc  mem    REG  253,0 99158720 399306 /usr/lib/locale/locale-archive
sleep   2201 libgcc    0u   CHR  136,0      0t0      3 /dev/pts/0
sleep   2201 libgcc    1u   CHR  136,0      0t0      3 /dev/pts/0
sleep   2201 libgcc    2u   CHR  136,0      0t0      3 /dev/pts/0
sleep   2201 libgcc    3w   REG  253,0        4 336396 /home/libgcc/cpp/mm.log
[libgcc@Heineken ~/cpp]$ ./cloexec 
fd for mm.log is : 3
child pid: 2216
[libgcc@Heineken ~/cpp]$ ps -ef|grep 221[6]
libgcc    2216     1  0 03:24 pts/0    00:00:00 sleep 100
[libgcc@Heineken ~/cpp]$ lsof -p 2216
COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
sleep   2216 libgcc  cwd    DIR  253,0     4096 319493 /home/libgcc/cpp
sleep   2216 libgcc  rtd    DIR  253,0     4096      2 /
sleep   2216 libgcc  txt    REG  253,0    25688 262402 /bin/sleep
sleep   2216 libgcc  mem    REG  253,0   151500 677607 /lib/ld-2.12.90.so
sleep   2216 libgcc  mem    REG  253,0  1889628 677608 /lib/libc-2.12.90.so
sleep   2216 libgcc  mem    REG  253,0 99158720 399306 /usr/lib/locale/locale-archive
sleep   2216 libgcc    0u   CHR  136,0      0t0      3 /dev/pts/0
sleep   2216 libgcc    1u   CHR  136,0      0t0      3 /dev/pts/0
sleep   2216 libgcc    2u   CHR  136,0      0t0      3 /dev/pts/0

 

### 关于 `TEMP_FAILURE_RETRY` 函数在打开 `/dev/device-mapper` 时的应用与故障排除 #### 使用场景分析 当调用 `open()` 打开设备文件(如 `/dev/device-mapper`),可能会因为临时错误而失败。这些错误通常由信号中断或其他瞬态条件引起。为了处理这种情况,可以使用 GNU 提供的 `TEMP_FAILURE_RETRY` 宏[^1]。 该宏的作用是对指定表达式进行重试操作,直到其返回值不再表示因信号中断而导致的错误为止。具体实现如下: ```c #define TEMP_FAILURE_RETRY(expression) \ ({ \ typeof(expression) _result; \ do { \ _result = (expression); \ } while (_result == -1 && errno == EINTR); \ _result; \ }) ``` 通过上述定义可以看出,如果 `expression` 的结果为 `-1` 并且 `errno` 值为 `EINTR`,则会重复执行此表达式直至成功或发生其他类型的错误[^4]。 #### 实际应用案例 假设我们需要以读写模式 (`O_RDWR`) 和关闭执行标志 (`O_CLOEXEC`) 来打开 `/dev/device-mapper` 设备,则可按照以下方式编写代码片段: ```c #include <fcntl.h> #include <unistd.h> int fd; fd = TEMP_FAILURE_RETRY(open("/dev/device-mapper", O_RDWR | O_CLOEXEC)); if (fd == -1) { perror("Failed to open /dev/device-mapper"); } else { printf("Successfully opened /dev/device-mapper\n"); } close(fd); ``` 在此例子中,我们利用了 `TEMP_FAILURE_RETRY` 对可能出现的短暂性错误进行了自动恢复尝试。 #### 故障排查建议 尽管有了这样的机制保护我们的 I/O 调用免受某些特定异常的影响,但在实际开发过程中仍需注意以下几个方面来进行有效的调试和维护工作: - **确认权限设置**:确保当前进程有足够的权限访问目标设备节点。 - **检查硬件状态**:有时物理存储介质本身存在问题也会引发类似的软件层面上看似无解的情况。 - **日志记录增强**:增加详细的日志输出可以帮助定位问题所在位置以及根本原因。 对于 Vue 或 Node.js 开发者来说,在面对诸如 “JavaScript heap out of memory” 错误时,除了调整虚拟机参数外,还应该考虑优化应用程序本身的性能表现,比如减少不必要的全局变量声明、及时释放未使用的资源对象等措施[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值