fcntl,非阻塞模式,CLOEXEC和管道pipe,execlp-del

文章目录


前言

介绍fcntl函数的两种基本用法,包括File descriptor flags和File status flags

管道使用的注意事项


1. fcntl

cpp 复制代码
#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

fcntl() 函数可对打开的文件描述符 fd 执行多种操作。具体操作有 cmd 参数指定。

该函数有可选的第三个参数,根据cmd决定是否有第三个参数。

不同的Linux内核版本只支持某些操作。建议通过下面的方法检查宿主机内核是否支持特定操作可通过如下方法: 调用fcntl()函数,传入对应的cmd参数,测试调用是否失败并EINVAL。

带(int)代表需要第三个参数,(void)代表不需要参数

复制文件描述符

  • FD_DUPFD (int)
    复制文件描述符 fd , 成功,返回新的文件描述符,ret >= arg,ret是大于等与arg的最小值
    和dup2有些相似,但又不一样
  • F_DUPFD_CLOEXEC (int)
    同上,但复制出的文件描述符增加close-on-exec标志

文件描述符标志

以下命令操纵与文件描述符相关的标志。当前只有FD_CLOEXEC标志

  • F_GETFD(void)
    会忽略arg(如果有)
  • F_SETFD (int)
    设置已经打开的文件描述符为close-on-exec

文件状态标志

每个打开的文件描述都有与之相关的状态标志,该标志由open初始化以及或可由fcntl修改。

  • F_GETFL (void)
    返回文件访问权限和文件状态标志,忽略arg
  • F_SETFL (int)
    设置文件状态标识,该命令仅支持O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK flags

2. 管道

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>


#include <stdio.h>
#include <string>
#include <exception>

class StringE: public std::exception
{
private:
    std::string msg;
public:
    StringE(const std::string& str):msg(str){}
    const char* what() const throw()
    {
        return msg.c_str();
    }
};
void throwStringException(const std::string str)
{
    fprintf(stderr, "\033[31m%s\033[0m\n", str.c_str());
    throw StringE(str);
    // try
    // {
    //     throw StringE(str);
    // }
    // catch(const std::exception& e)
    // {
    //     fprintf(stderr, "\033[31m%s\033[0m\n", e.what());
    //     throw StringE(str);
    // }
}
#ifndef NDEBUG
#define ERROR(msg,...) fprintf(stderr, "\033[33m" msg "\033[0m\n", ##__VA_ARGS__)
#else
#define ERROR(msg,...)
#endif
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define THROWSTR(str)  throwStringException(str " " __FILE__ ":" TOSTRING(__LINE__) )

class Pipe
{
public:
    enum {pread = 0, pwrite = 1, father_read, child_write, father_write, child_read};

private:
    int p1[2]; // 0 read 1 write father read     child write
    int p2[2];                   //father write  child read
public:
    Pipe()
    {
        p1[pread] = p1[pwrite] = p2[pread] = p2[pwrite] = -1;
        bool rc = (pipe(p1) == 0) && (pipe(p2) == 0);
        if (false == rc)
        {
            perror("pipe");
            this->~Pipe();
            THROWSTR("pipe");
        }
    }
    ~Pipe()
    {
        if (p1[pread] != -1)  { ::close(p1[pread]); p1[pread] = -1; }
        if (p1[pwrite] != -1) { ::close(p1[pwrite]);p1[pwrite] = -1;}
        if (p2[pread] != -1)  { ::close(p2[pread]); p2[pread] = -1; }
        if (p2[pwrite] != -1) { ::close(p2[pwrite]);p2[pwrite] = -1;}
        
    }
    int getfd(int type)
    {
        switch (type)
        {
        case father_read:
            return p1[pread];
            break;
        case child_write:
            return p1[pwrite];
            break;
        case father_write:
            return p2[pwrite];
            break;
        case child_read:
            return p2[pread];
            break;
        
        default:
            break;
        }
        return -1;
    }
    void pclose(int type)
    {
        switch (type)
        {
        case father_read:
            ::close(p1[pread] ); p1[pread] = -1;
            break;
        case child_write:
            ::close(p1[pwrite]); p1[pwrite] = -1;
            break;
        case father_write:
            ::close(p2[pwrite]); p2[pwrite] = -1;
            break;
        case child_read:
            ::close(p2[pread] );  p2[pread] = -1;
            break;
        
        default:
            assert(0);
            break;
        }
    }
};
  1. 管道属于操作系统对象,不受进程控制

  2. 当没有引用指向管道时,系统会回收该对象

    匿名管道:
    阻塞模式下:
    在读写端都不关闭时,阻塞式读写;如果管道内可暂存下写的数据,则write可返回,否则阻塞,若write阻塞中时读端关闭则write立即收到SIGPIPE信号
    管道写端write完后关闭,read读端会立即返回0,表示EOF
    write管道时如果读端已经关闭了,write立即收到SIGPIPE信号.
    非阻塞模式:
    读写端都打开,write发数据,read读数据;当管道满时,write返回-1,errno==EAGAIN代表Resource temporarily unavailable;
    当管道空时,read返回-1,errno==EAGAIN代表Resource temporarily unavailable;
    当写端关闭时,read返回0表示EOF
    当读端关闭时,write收到SIGPIPE信号,返回-1,errno==EPIPE
    有名管道:
    阻塞式:
    先open的一方会阻塞在open处,只有当有名管道的两端都open了才会继续执行
    阻塞式写,直到所有数据都写完,返回写入数据的字节数,若write时对端关闭,则收到SIGPIPE信号
    阻塞读,返回读取到的字节数,若对段关闭则立即返回0表示EOF
    非阻塞式:
    读写都不会阻塞在open处;先打开读read立即返回0,表示EOF;先打开写端,以只写方式打开写端,open失败;
    只能先打开读端
    read返回即返回0代表EOF,
    <open写段>, read==0,errno==EAGAIN,
    <write一帧数据后read到数据>,
    然后read返回-1,errno==0
    写端关闭后,read==0
    read返回-1,errno==EAGAIN代表对端已经打开,需要再次尝试接收数据
    write成功立即返回写入的字节数,当写入速度较快管道的暂存区满时,返回-1,errno==EAGAIN
    write时对端关闭,收到SIGPIPE信号返回-1,errno==EPIPE

管道小结  速记

write的对端关闭,收到SIGPIPE信号返回-1,errno==EPIPE

read的对端关闭,立即返回0表似乎EOF

在write完少量数据返回后立即关闭写端,读端打开但不read,此时数据会暂时放在管道中

3. execlp

cpp 复制代码
int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);

#include <unistd.h>
#include <stdio.h>

int main() {
    // 调用 ls 命令并传递参数
    if (execlp("ls", "ls", "-l", "/etc/", (char *)NULL) == -1) {
        perror("execlp failed");
    }
    return 0;
}

在这个例子中,execlp 调用 ls -l /etc/ 命令。

第二个参数一般填入执行的命令名本身,参数为可变长度但必须以NULL结尾

无论arg有几个,main的argc始终>=1; argv[0]就是第二个参数

若第二个参数直接是NULL,则main的argc==1, argv[0]为空

cpp 复制代码
int main(int argc, char* argv[]) {
    printf("--:[%d] [%s]\n", argc, argv[0]);
    return 0;

    // 调用 ls 命令并传递参数
    if (execlp("./b.out", "dsf", "sjdf", NULL) == -1) {
        perror("execlp failed");
    }
    return 0;
}

总结

设置文件描述符为非阻塞和 close-on-exec

cpp 复制代码
int fd = 0;
int ret;
// File descriptor flags
ret = fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); assert(ret == 0);
    
// File status flags
ret = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); assert(ret == 0);

// 在文件打开时
open("file", O_RDWR | O_CLOEXEC | O_NONBLOCK);
相关推荐
楼田莉子2 小时前
C++17新特性:__had_include/属性/求值顺序规则
开发语言·c++·后端
h_a_o777oah3 小时前
状态机+划分型 DP :深度解析K-划分问题下 DP 状态的转移逻辑(洛谷P2679 P2331 附C++代码)
c++·算法·动态规划·acm·状态机dp·划分型dp·滚动数组优化
雪度娃娃5 小时前
Asio异步读写——连接的安全回收问题
开发语言·c++·安全·php
不吃土豆的马铃薯5 小时前
Spdlog 进阶:日志基本控制、日志格式控制、异步记录器
linux·服务器·开发语言·前端·c++
liulilittle5 小时前
TCP UCP:基于卡尔曼滤波的BBR增强型拥塞控制算法
linux·网络·c++·tcp/ip·算法·c·通讯
咩咦6 小时前
C++学习笔记26:static 静态成员
c++·学习笔记·static·静态成员变量·静态成员·静态成员函数
秋落风声6 小时前
内存池仿Nginx C++实现
c++·nginx
小白要努力sgy6 小时前
实时通信框架CyberRT
c++·自动驾驶·实时通讯
hansang_IR6 小时前
【记录】loj2967「COCI 2010.03.06」PROGRAM
c++·算法
霍霍的袁8 小时前
【C++初阶】缺省参数(默认参数)详细讲解
开发语言·c++·算法