open函数中O_CLOEXEC标志的开关

本文深入解析了Linux内核中O_CLOEXEC flag的功能和实现方式,包括如何在多线程环境下避免竞态条件,并通过在open函数中设置此标志来简化文件描述符管理。同时,文章还介绍了如何在编译时启用此新功能,以及其在不同程序中的应用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


 

<think>好的,我现在需要帮用户了解C语言中open函数的O_CREAT参数的作用和用法。用户已经提供了几个相关的引用内容,我需要结合这些信息来组织回答。首先,我需要明确O_CREAT的基本作用是什么。根据引用中的信息,O_CREAT用于在文件不存在时创建新文件。但使用这个标志时需要指定文件权限模式,也就是第三个参数mode_t mode,否则可能会出现权限问题,比如引用3提到的错误。 接下来,我要整理O_CREAT的使用方法。根据引用2,open函数的原型是int open(const char *pathname, int flags, mode_t mode),当使用O_CREAT时,必须提供mode参数。例如,open("file", O_CREAT | O_RDWR, 0644)。如果省略mode参数,会导致未定义行为,可能文件权限不正确,比如引用3中的例子没有指定mode,导致文件权限可能是随机值。 然后需要提到常见错误,如引用3中的代码示例,用户在使用O_CREAT时忘记传递mode参数,导致权限问题。这时候需要强调mode参数的重要性,并给出正确的写法,比如添加第三个参数如0644。同时,可能需要解释权限位的八进制表示,比如0644对应的权限是rw-r--r--。 另外,结合引用1中的内容,当flags参数中出现O_CLOEXEC或O_LARGEFILE时,这些宏对应的值可能与O_CREAT组合使用,但用户的问题主要集中在O_CREAT本身,所以这部分可能不需要深入,但可以作为相关知识提及。 最后,生成相关问题的时候,要围绕open函数的使用、标志组合、错误处理等方面提问,确保问题与用户的问题相关,并能引导进一步学习。例如,如何正确组合多个标志,O_EXCL的作用,以及文件权限设置的问题。 需要确保回答结构清晰,分点说明,并给出代码示例和错误示例,帮助用户理解。同时,引用相关的参考资料,比如引用2和3中的内容,说明错误原因和正确用法。检查是否所有LaTeX格式正确,数学表达式用$包裹,独立公式用$$,但在这个问题中可能涉及较少数学公式,主要是代码和参数说明,所以重点检查代码块和参数格式是否正确。 现在,整理这些思路,分步骤解释O_CREAT的作用、用法、常见错误及注意事项,并附上示例代码和修正后的代码。确保回答符合用户的要求,使用中文,引用正确,并生成相关问题。</think>### open函数中O_CREAT参数的作用及用法 #### 1. O_CREAT的作用 O_CREAT是Linux/Unix系统文件操作函数`open`的标志参数之一,主要功能是:**当文件不存在时,创建新文件**。若未指定此标志且文件不存在,`open`函数会返回错误[^2][^3]。 #### 2. 基本语法 ```c #include <fcntl.h> int open(const char *pathname, int flags, mode_t mode); ``` - **当flags包含O_CREAT时,必须指定第三个参数`mode`**(文件权限模式)。 - 示例:创建可读写的文件,权限为`rw-r--r--` ```c int fd = open("data.txt", O_CREAT | O_RDWR, 0644); ``` #### 3. 常见错误示例 以下代码会导致文件权限异常(可能生成随机权限,如`rwS--s--t`)[^3]: ```c // 错误:未指定mode参数 fd = open(filepath, O_CREAT | O_RDWR | O_APPEND); ``` **修正方法**:添加权限参数,如`0644`(八进制数表示权限)。 #### 4. 权限模式说明 - 权限值使用**八进制**表示,例如: - `0644` → 所有者可读写,其他用户只读 - `0755` → 所有者可读/写/执行,其他用户读/执行 - 实际权限受系统`umask`影响,最终权限为`mode & ~umask`。 #### 5. 组合使用场景 | 组合标志 | 行为描述 | |-------------------------|----------------------------------| | `O_CREAT | O_EXCL` | 文件必须不存在,否则返回错误 | | `O_CREAT | O_TRUNC` | 若文件已存在,清空内容 | | `O_CREAT | O_APPEND` | 写入时追加到文件末尾 | #### 6. 完整示例代码 ```c #include <fcntl.h> #include <unistd.h> int main() { const char *path = "test.txt"; int fd = open(path, O_CREAT | O_RDWR | O_APPEND, 0644); // 正确写法 write(fd, "Hello", 5); close(fd); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值