在半双工串行端口上设计完全异步 IO 时,我需要您的建议。目前我有一个读者线程和许多由信号量和互斥锁控制的作者线程。现在我想通过消除线程来简化同步。主要问题是串行端口 IO 有一个奇怪的行为。
我只需要确保 read
和 write
系统调用只阻塞调用线程,直到 IO 操作实际完成。我假设 read
默认是一个阻塞系统调用。尽管我得到 -1
作为 read
的返回值。有一个奇怪的 EBUSY
错误,我没有对此进行描述。当前代码:
bool SerialManager::initialize(const PortType& portType, const size_t& number)
{
// Open Serial port (/dev/ttyS2 in this case)
fd = open(portName.str().c_str(), O_RDWR ); //O_NOCTTY
if (fd < 0) // if open is not successful
{
cerr << ERROR << "Unable to open `" << portName << "'." << endl;
return false;
}
else
{
cout << INFO << "Port " << portName.str() << " successfully opened."
<< endl;
cout << INFO << "Configuring port..." << endl;
fcntl(fd, F_SETFL,~O_NONBLOCK);
struct termios port_settings; // structure to store the port settings in
cfsetispeed(&port_settings, B38400); // set baud rate
cfsetospeed(&port_settings, B38400); // set baud rate
port_settings.c_cflag |= CLOCAL | CREAD;
port_settings.c_cflag &= ~CRTSCTS; // disable H/W flow control
port_settings.c_lflag &= ~( ISIG | // disable SIGxxxx signals
IEXTEN | // disable extended functions
ECHO | ECHOE); // disable all auto-echo functions
port_settings.c_lflag &= ~ICANON ; // raw mode
port_settings.c_oflag &= ~OPOST; // raw output
port_settings.c_iflag &= ~(IXON | IXOFF | IXANY); // disable S/W flow control;
port_settings.c_cc[VTIME] = 20; // wait 0.1 second to get data
port_settings.c_cc[VMIN] = 0;
port_settings.c_cflag = (port_settings.c_cflag &= ~CSIZE) | CS8; // set data byte size
port_settings.c_cflag &= ~CSTOPB; // set stop bit 1
port_settings.c_cflag &= ~PARENB; // set no parity
port_settings.c_iflag |= IGNPAR; // ignore parity
port_settings.c_iflag &= ~(INPCK | ISTRIP | PARMRK);
// Set
if (tcsetattr(fd, TCSANOW, &port_settings) < 0)
{
cerr << ERROR << "Unable to configure serial port." << endl;
return false;
}
else
{
cout << INFO << "Port `" << portName.str()
<< "' configuration was successful." << endl;
return true;
}
}
}
写入数据:
int SerialManager::asyncWriteData(const byte* data, const size_t& size)
{
int writeSize = write(fd, data, size);
return writeSize;
}
阅读:
void SerialManager::asyncRead(byte* buffer, const size_t& size, bool& ok)
{
byte temp[256];
ssize_t packetSize = read(fd, temp, 256);
if (packetSize > 0)
{
for (size_t i = 0; i < size; ++i)
buffer[i] = temp[i];
ok = true;
}
cout << errno << endl;
perror("Error occured: "); // <=== Here I'm getting EBUSY (code 16)
ok = false;
}
在外部使用 SerialManager
类:
....
word checksum = this->id + 0x2C;
checksum = ~checksum;
// Send read command
byte command[] =
{ 0xff, // heading
0xff, // ~
this->id, // id of actuator
0x04, // length
0x02, // instruction: read
0x24, // start address: present position
0x02, // data length
static_cast<byte>(checksum) //checksum
};
SerialManager::lockPort(); // lock a mutex to avoid collitions
int numbytes = SerialManager::asyncWriteData(command, 8);
if (numbytes < 0)
{
cerr << ERROR << "Could not write to serial port." << endl;
return 0;
}
cout << INFO << numbytes << " bytes has been written." << endl;
for (size_t i = 0; i < 8; ++i)
{
cout << hex << setfill('0') << setw(2) << (int) command[i] << ' ';
}
cout << endl;
byte* data = new byte[8];
bool ok;
// Here I need to make sure data write is completed before start reading
SerialManager::asyncRead(data, 8, ok);
if (ok)
{
word position = data[5] + (static_cast<word>(data[6]) << 8);
return position;
}
else
{
cerr << ERROR << "Unable to read data from serial port..." << endl;
return -1;
}
SerialManager::unlockPort(); // Unlock previously locked mutex
....
更新:
我删除了毫无意义的读者线程。因为我们有一条无法控制传输的半双工线路。同步IO有两个问题:
从 Controller 向执行器发送一个非常长的数据,当数据在端口上时,第一个执行器无响应地响应:
执行器可能会在另一个执行器的数据未完全传输时做出响应
EBUSY
的问题也通过在write
之后添加fsync
来解决。这就是我所需要的。 (一个阻塞的write
):
int SerialManager::asyncWriteData(const byte* data, const size_t& size)
{
ssize_t packetSize = write(fd, data, size);
if (packetSize > 0)
{
fsync(fd);
}
return packetSize;
}
来自 man fsync:
fsync() transfers ("flushes") all modified in-core data of (i.e., modified buffer cache pages for) the file referred to by the file descriptor fd to the disk device (or other permanent storage device) where that file resides. The call blocks until the device reports that the transfer has completed. It also flushes metadata information associated with the file
最佳答案
我怀疑这是否能回答您的问题,但我认为它会有所帮助。
在void SerialManager::asyncRead(byte*, const size_t&, bool&)中,有两个错误:
当 read 返回成功时,您正在使用 errno 的值(并使用 perror 根据它打印消息),而不仅仅是在它出错时(返回值 < 0)。成功时,未指定 errno 具有任何特定值。所以你在有效地使用垃圾数据;您得到的 EBUSY 可能没有任何意义。 errno 仅在发生特定错误时才定义为具有特定值。请参阅 Linux 系统上的“man errno”,它在我的 Debian 系统上的相关部分中说:“只有当调用的返回值指示错误时,它的值才有意义(即,-1 来自大多数系统调用;-1或来自大多数库函数的 NULL);允许成功的函数更改 errno。”。请注意“允许”,而不是“必须”。
您读入一个临时缓冲区,然后将其逐字节复制到调用者传递的目标缓冲区中。这是对 CPU 周期和内存带宽的毫无意义的浪费。您可以直接读入调用者的缓冲区。即使您必须(出于某种奇怪的原因)读入临时缓冲区然后将其复制到其他地方,您也应该使用 memcpy(3) 或 memmove(3) 将其复制到其他地方,而不是使用您自己的逐字节循环。
关于设计,我不明白在只有一个串口的情况下有多个读写器有什么意义。你想实现并行吗?我不明白你怎么能,只有一个串口。 如果你想要异步而不是并行性,你应该调查 fcntl(fd, F_SETFL, existing_flags & O_ASYNC) (尽管信号相当陈旧和丑陋),或者可能只是将 fd 设置为非阻塞,然后在内部事件中调用 select()应用程序循环并处理任何可用的 I/O(如果有)。
希望这对您有所帮助!
关于c++ - 串口异步IO,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13921870/