文章目录
前言
介绍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;
}
}
};
-
管道属于操作系统对象,不受进程控制
-
当没有引用指向管道时,系统会回收该对象
匿名管道:
阻塞模式下:
在读写端都不关闭时,阻塞式读写;如果管道内可暂存下写的数据,则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);