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);
相关推荐
IronmanJay1 小时前
【LeetCode每日一题】——862.和至少为 K 的最短子数组
数据结构·算法·leetcode·前缀和·双端队列·1024程序员节·和至少为 k 的最短子数组
Ddddddd_1581 小时前
C++ | Leetcode C++题解之第504题七进制数
c++·leetcode·题解
J_z_Yang1 小时前
LeetCode 202 - 快乐数
c++·算法·leetcode
加载中loading...2 小时前
Linux线程安全(二)条件变量实现线程同步
linux·运维·服务器·c语言·1024程序员节
Wx120不知道取啥名2 小时前
C语言之长整型有符号数与短整型有符号数转换
c语言·开发语言·单片机·mcu·算法·1024程序员节
biomooc3 小时前
R语言 | paletteer包:拥有2100多个调色板!
r语言·数据可视化·1024程序员节
Hello.Reader3 小时前
FFmpeg 深度教程音视频处理的终极工具
ffmpeg·1024程序员节
Y.O.U..4 小时前
STL学习-容器适配器
开发语言·c++·学习·stl·1024程序员节
lihao lihao4 小时前
C++stack和queue的模拟实现
开发语言·c++
就爱敲代码4 小时前
怎么理解ES6 Proxy
1024程序员节