open函数中O_CLOEXEC标志的开关

本文详细介绍了O_CLOEXEC标志在Linux系统调用中的作用及其使用方法,包括如何避免竞态条件,以及如何在编译时正确启用此功能。

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

 

这个是apsd 的包 解释一下代码 int main(int argc, char *argv[]) { struct apsd_context ctx; struct mod_context *mod, *mod_next; int i; int ch; const char *config_path = "/etc/apsd.json"; const char *pid_path = "/tmp/apsd.pid"; #define MAX_PID_LEN (16) char pid[MAX_PID_LEN]; int fd = open(pid_path, O_RDWR|O_CREAT|O_CLOEXEC); if ( -1 == fd ) APSD_DIE("open pid_file error:%d\n", errno); if ( flock(fd, LOCK_EX|LOCK_NB) < 0 ) { APSD_DIE("pid file locked."); } lseek(fd, 0, SEEK_SET); memset(pid, 0, sizeof(pid)); snprintf(pid, sizeof(pid), "%d", getpid()); write(fd, pid, sizeof(pid)); signal(SIGUSR1, adust_dbg_lvl); signal(SIGUSR2, adust_dbg_lvl); while ((ch = getopt(argc, argv, "c:hl:")) != -1) { switch (ch) { case 'c': config_path = optarg; break; case 'l': g_apsd_dgb_lvl = atoi(optarg); if (g_apsd_dgb_lvl >= LVL_MAX) g_apsd_dgb_lvl = LVL_MAX - 1; else if (g_apsd_dgb_lvl <= LVL_MIN) g_apsd_dgb_lvl = LVL_MIN + 1; APSD_INFO("g_apsd_dgb_lvl=%d!", g_apsd_dgb_lvl); break; case 'h': usage(argv[0]); return 0; default: usage(argv[0]); return 1; } } /* Initialize myself. */ memset(&ctx, 0, sizeof(ctx)); INIT_LIST_HEAD(&ctx.mod_list); config_load(&ctx.conf, config_path); if (!ctx.conf.enable) { exit(0); } #ifdef CONFIG_TP_QCA_TS_SUPPORT traffic_separation_config_load(&ctx.conf); #endif config_bridge(&ctx.conf); event_init(&ctx.ev); /* Initialize modules. */ for (i = 0; i < ARRAY_SIZE(mod_init_list); i++) { mod = mod_init_list[i](&ctx); if (mod) { APSD_INFO("%s initialized!", mod->name); list_add(&mod->list, &ctx.mod_list); } } if (apsd_get_work_mode() != WORK_MODE_RE) { APSD_INFO("work mode is AP!"); event_config_for_ap(&ctx.ev); config_bridge_revert(&ctx.conf); } else { APSD_INFO("work mode is RE!"); event_loop(&ctx.ev); config_bridge_revert(&ctx.conf); } /* Free modules. */ list_for_each_entry_safe(mod, mod_next, &ctx.mod_list, list) { list_del(&mod->list); mod->free(mod); } closelog(); close(fd); event_free(&ctx.ev); config_free(&ctx.conf); return 0; }
12-03
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <pthread.h> #include <sys/mman.h>  //ioctl头文件 #include <sys/ioctl.h> #include <linux/videodev2.h> //V4L2架构有关的头文件 #include "inc/DRMwrap.h" #include "inc/show_bmp.h" #include "inc/touch.h" #define DRM_PATH "/dev/dri/card0" /*     1.打开DRM设备     2.初始化DRM设备     3.申请一块映射到用户空间的显存     4.把像素值写入到内存地址     5.写完之后要及时刷新DRM设备     6.关闭DRM设备,释放资源 */ int de = -1;//摄像头打开判断 int cm_bmp[640*480];//获取ARGB一帧数据 int drm_fd; struct drmHandle drm; int camerafd; int *drm_p; int x=0,y=0; int pai=0; struct usraddr *alladdr; int* drm_init() {     drm_fd = open(DRM_PATH,O_RDWR | O_CLOEXEC);     if(drm_fd == -1)     {         perror("open drm error");         return NULL;     }     // 初始化 DRM 设备,并获得连接器信息     DRMinit(drm_fd);         bzero(&drm,sizeof(drm));     DRMcreateFB(drm_fd, &drm);     drm_p = (int*)drm.vaddr; //映射到用户空间的显存地址 } void drm_exit() {     DRMfreeResources(drm_fd, &drm);     close(drm_fd); } /*     使用V4L2架构提供的宏定义和结构体,枚举实现对摄像头的操作     R = Y + 1.14V     G = Y - 0.39U - 0.58V     B = Y + 2.03U         R = Y + 1.40 *(V-128)     G = Y – 0.3455 *(U –128) – 0.7169 *(V –128)     B = Y + 1.779 *(U – 128) */ //封装成结构体存放每个缓存块的首地址和长度 struct usraddr {     int somelength; //缓存的大小     void *someaddr; //缓存的首地址 }; //封装yuv转换成rgb的函数 int yuvtorgb(int y,int u,int v) {     int point;     //YUYV-->两组RGB     int r,g,b; //定义了rgb三个分量     b = 1164*(y - 16)/1000 + 2018*(u - 128)/1000;     g = 1164*(y - 16)/1000 - 813*(v - 128)/1000 - 391*(u - 128)/1000;     r = 1164*(y - 16)/1000 + 1596*(v - 128)/1000;     //处理值越界的情况     if(r>255)         r=255;     if(g>255)         g=255;     if(b>255)         b=255;     if(r<0)         r=0;     if(g<0)         g=0;     if(b<0)         b=0; //将rgb转换成ARGB返回     point=0x00<<24|r<<16|g<<8|b;     return point; } //封装将一整个画面yuv数据--》rgb数据的函数 int yuvbuftorgbbuf(char *yuvbuf,int *rgbbuf)//  10 {     int i,j;     for(i=0,j=0; j<640*480; i+=4,j+=2)     {         rgbbuf[j]=yuvtorgb(yuvbuf[i],yuvbuf[i+1],yuvbuf[i+3]);         rgbbuf[j+1]=yuvtorgb(yuvbuf[i+2],yuvbuf[i+1],yuvbuf[i+3]);     }     /* yuvtorgb(yuvbuf[0],yuvbuf[1],yuvbuf[3])     yuvtorgb(yuvbuf[2],yuvbuf[1],yuvbuf[3])         yuvtorgb(yuvbuf[4],yuvbuf[5],yuvbuf[7])     yuvtorgb(yuvbuf[6],yuvbuf[5],yuvbuf[7]) */ } int camera_init() {     //打开摄像头的驱动     camerafd=open("/dev/video6",O_RDWR);     if(camerafd==-1)     {         perror("打开摄像头的驱动失败!\n");         return -1;     }     //设置摄像头的采集格式     struct v4l2_format myfmt;     bzero(&myfmt,sizeof(myfmt));     myfmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;     myfmt.fmt.pix.width=640;//可以缩小,但是需要按比例缩小     myfmt.fmt.pix.height=480;     myfmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV; //yuv格式     myfmt.fmt.pix.field =V4L2_FIELD_NONE;         int ret=ioctl(camerafd,VIDIOC_S_FMT,&myfmt);     if(ret==-1)     {         perror("设置采集格式失败!\n");         return -1;     }     //申请缓存     struct v4l2_requestbuffers req;     bzero(&req,sizeof(req));     req.count=4;  //缓冲块数量,一般1---5之间,一个缓冲块存放一帧画面     req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;     req.memory=V4L2_MEMORY_MMAP;     ret=ioctl(camerafd,VIDIOC_REQBUFS,&req);     if(ret==-1)     {         perror("申请缓存失败!\n");         return -1;     }     //定义指针分配堆空间存放每个缓存块的信息     alladdr = calloc(4,sizeof(struct usraddr));         //分配你刚才申请的缓存     for(int i=0; i<4; i++)     {         struct v4l2_buffer buf;         bzero(&buf,sizeof(buf));         buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;         buf.memory=V4L2_MEMORY_MMAP;         buf.index=i;  //你申请的缓冲块的索引,从0开始         ret=ioctl(camerafd,VIDIOC_QUERYBUF,&buf);         if(ret==-1)         {             perror("分配缓存失败!\n");             return -1;         } //趁热打铁,马上映射分配得到的某一块缓存         alladdr[i].someaddr=mmap(NULL,buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,camerafd,buf.m.offset);         if(alladdr[i].someaddr==NULL)         {             perror("映射首地址失败!\n");             return -1;         }         alladdr[i].somelength=buf.length;     }     //入队     for(int i=0; i<4; i++)     {         struct v4l2_buffer buf;         bzero(&buf,sizeof(buf));         buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;         buf.memory=V4L2_MEMORY_MMAP;         buf.index=i;  //你申请的缓冲块的索引,从0开始         ret=ioctl(camerafd,VIDIOC_QBUF,&buf);         if(ret==-1)         {             perror("入队失败!\n");             return -1;         }     }     //开始采集     enum v4l2_buf_type mytype;     mytype=V4L2_BUF_TYPE_VIDEO_CAPTURE;     ret=ioctl(camerafd,VIDIOC_STREAMON,&mytype);     if(ret==-1)     {         perror("开始采集失败!\n");         return -1;     } } int camera_show()//显示摄像头捕获的画面到开发板 {     //定义指针存放yuv转换得到的完整rgb     int *rgbbuf=malloc(640*480*4);     //定义超时时间1秒     struct timeval mytime;     bzero(&mytime,sizeof(mytime));     mytime.tv_sec=1;     int ret;     //循环入队出队显示视频流,顺便用多路复用检测是否真的开始采集有数据了     while(1) //fd1  fd2  fd3 --》FD_ISSET()     {         if(de == 0)break;         fd_set myset;         FD_ZERO(&myset);    // 初始化 myset,清除所有文件描述符         FD_SET(camerafd,&myset);//将文件描述符 fd 添加到 myset 集合中         select(camerafd+1,&myset,NULL,NULL,&mytime);         //检测到有数据了,就立马出队并显示出来 or(int i=0; i<4; i++)         {             struct v4l2_buffer buf;             bzero(&buf,sizeof(buf));             buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;             buf.memory=V4L2_MEMORY_MMAP;             buf.index=i;  //你申请的缓冲块的索引,从0开始             ret=ioctl(camerafd,VIDIOC_DQBUF,&buf);             if(ret==-1)             {                 perror("出队失败!\n");                 return -1;             }             ret=ioctl(camerafd,VIDIOC_QBUF,&buf);             if(ret==-1)             {                 perror("入队失败!\n");                 return -1;             }             //将出队的画面数据显示在开发板的液晶屏上 yuv格式-->rgb格式显示             yuvbuftorgbbuf(alladdr[i].someaddr,rgbbuf);             //将转换得到的ARGB显示在开发板的液晶屏上             for(int j=0; j<480; j++)             {                 memcpy(drm_p+j*1024,&rgbbuf[j*640],640*4);             }             if(pai == 1)             {                 pai=0;                 for(int i=0; i<640*480; i++)                 {                     cm_bmp[i]=rgbbuf[i];                 }             }             DRMshowUp(drm_fd,&drm);         }     } } void *camera_on() {     static int num = 56 ;     drm_init();//初始化DRM     camera_init();//初始化预处理摄像头     camera_show();//打开摄像头显示画面     pthread_exit(&num);//被返回的地址不允许返回栈空间的内存地址 } int main() {     pthread_t tid ;     //创建线程       int lcd_fd = lcd_open();     int a =-1;     while (1)     {         scanf("%d",&a);         if(a==1)         {             if(de == 1)             {                 printf("重复打开了\n");                 continue;             }             printf("打开摄像头1111111111111\n");             int ret_val = pthread_create( &tid, NULL , camera_on , NULL );             if(ret_val)             {                 fprintf( stderr , "线程创建失败:%s\n" , strerror(ret_val));                 return -1 ;             }             de = 1;         }         else if(a == 2)         {             if(de == 0)             {                 printf("重复关闭了\n");                 continue;             }             de = 0;             int * p ;             printf("正在等待子线程退出...\n");             //pthread_join 会修改 指针 p的指向 (因此这里使用的是地址传递)             pthread_join(tid , (void**)&p); //void **retval  阻塞等待子线程退出             printf("子线程已经退出:%d\n" , *p );             drm_exit();//关闭DRM         }             }     lcd_close(lcd_fd);     return 0; } 在这个代码的基础上,只实现基本功能,完整代码。项目功能: 1.基础功能 a.实现智能安防监控系统(摄像头捕获画面显示在开发板上面) b.能够通过开发板的触摸屏控制摄像头的开关(画几个按钮,点击按钮实现摄像头开或关) c.关闭摄像头后实现图片播放
06-27
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值