在读一读冰羚代码(1)从demo开始-优快云博客介绍过,冰羚的运行需要先启动iox-roudi进程。iox-roudi进程的入口函数为roudi_main.cpp中的main函数:
int main(int argc, char* argv[]) noexcept
{
using iox::roudi::IceOryxRouDiApp;
iox::config::CmdLineParserConfigFileOption cmdLineParser;
auto cmdLineArgs = cmdLineParser.parse(argc, argv);
if (cmdLineArgs.has_error())
{
IOX_LOG(Fatal, "Unable to parse command line arguments!");
return EXIT_FAILURE;
}
if (!cmdLineArgs.value().run)
{
return EXIT_SUCCESS;
}
iox::config::TomlRouDiConfigFileProvider configFileProvider(cmdLineArgs.value());
auto config = configFileProvider.parse();
if (config.has_error())
{
auto errorStringIndex = static_cast<uint64_t>(config.error());
IOX_LOG(Fatal,
"Couldn't parse config file. Error: "
<< iox::roudi::ROUDI_CONFIG_FILE_PARSE_ERROR_STRINGS[errorStringIndex]);
return EXIT_FAILURE;
}
IceOryxRouDiApp roudi(config.value());
return roudi.run();
}
先运行./iox-roudi -h看下参数说明:
如上-c参数的说明至关重要,-c参数后提供了内存池的配置文件路径,如果用户没有提供该文件,则会优先查找/etc/iceoryx/roudi_config.toml,如果此文件也不存在,最后使用默认的参数设置。配置文件大概长这个样子:
每个segment对应一个内存池,segment下的各个item对应不同内存的大小及数量配置,以上配置文件只给出了一个内存池的配置,对应的内存池如下图所示:
内存大小的配置以字节为单位。为了提高内存的分配效率,内存池一般是一段预先分配好的内存,为了容纳不同大小的数据结构,内存池内部一般会进一步划分为更小粒度大小的内存。比如一个数据结构的大小为256字节,则需要从上述提到的内存池中拿出一段大小为1024字节的内存来装载这个数据结构。
内存池的配置文件使用toml文件格式。toml文件(Tom's Obvious, Minimal Language)是一种专为配置文件设计的格式,强调易读性和简洁性。它的语法类似 INI
文件,但支持更复杂的数据结构和严格的类型定义,适合描述层级化配置。toml文件使用开源cpptoml库解析(https://github.com/skystrife/cpptoml)。
下面看一看iox-roudi配置文件的代码解析过程:
using iox::roudi::IceOryxRouDiApp;
iox::config::CmdLineParserConfigFileOption cmdLineParser;
auto cmdLineArgs = cmdLineParser.parse(argc, argv); (1)
if (cmdLineArgs.has_error())
{
IOX_LOG(Fatal, "Unable to parse command line arguments!");
return EXIT_FAILURE;
}
if (!cmdLineArgs.value().run)
{
return EXIT_SUCCESS;
}
(1)解析命令行参数,返回值cmdLineArgs的类型如下:
struct CmdLineArgs_t
{
bool run{true};
RouDiConfig roudiConfig; (1)
roudi::ConfigFilePathString_t configFilePath; (2)
};
(1)roudi进程的个性化参数设置,各个参数的含义及作用后面会有专门的文章解析
(2)toml的文件路径,根据toml文件的内容建立共享内存上的内存池供数据发布者和订阅者进程使用
TomlRouDiConfigFileProvider::parse()解析toml文件的各个字段,如果无toml文件则使用默认设置,返回值IceoryxConfig的类型如下:
template <typename... ConfigParts>
struct Config : public ConfigParts...
{
Config& setDefaults() noexcept
{
helper::SetDefaults<ConfigParts...>::apply(this);
return *this;
}
template <typename T>
Config& setModuleDefaults() noexcept
{
T::setDefaults();
return *this;
}
Config& optimize() noexcept
{
helper::Optimize<ConfigParts...>::apply(this);
return *this;
}
};
using IceoryxConfig = Config<mepoo::SegmentConfig, config::RouDiConfig>; (1)
(1)表明IceoryxConfig的类型为Config,Config继承其模板类型SegmentConfig和RouDiConfig。
SegmentConfig中存储toml文件中内存池的配置,RouDiConfig存储roudi进程的个性化设置。根据Config的setDefaults函数定义,调用IceoryxConfig对象的setDefaults函数,会依次调用SegmentConfig对象的setDefaults函数和RouDiConfig对象的setDefaults函数。TomlRouDiConfigFileProvider::parse()的实现如下:
iox::expected<iox::IceoryxConfig, iox::roudi::RouDiConfigFileParseError> TomlRouDiConfigFileProvider::parse() noexcept
{
// Early exit in case no config file path was provided
if (m_customConfigFilePath.empty())
{
iox::IceoryxConfig defaultConfig;
defaultConfig.setDefaults(); (1)
static_cast<RouDiConfig&>(defaultConfig) = m_roudiConfig;
return iox::ok(defaultConfig);
}
std::ifstream fileStream{m_customConfigFilePath.c_str()};
if (!fileStream.is_open())
{
IOX_LOG(Error, "Could not open config file from path '" << m_customConfigFilePath << "'");
return iox::err(iox::roudi::RouDiConfigFileParseError::FILE_OPEN_FAILED);
}
return TomlRouDiConfigFileProvider::parse(fileStream).and_then([this](auto& config) {
static_cast<RouDiConfig&>(config) = m_roudiConfig;
});
}
(1)如toml文件不存在,则使用代码里面的hard code配置:
SegmentConfig& SegmentConfig::setDefaults() noexcept
{
m_sharedMemorySegments.clear();
auto groupName = PosixGroup::getGroupOfCurrentProcess().getName(); (1)
m_sharedMemorySegments.push_back({groupName, groupName, MePooConfig().setDefaults()});
return *this;
}
MePooConfig& MePooConfig::setDefaults() noexcept (2)
{
m_mempoolConfig.push_back({128, 10000});
m_mempoolConfig.push_back({1024, 5000});
m_mempoolConfig.push_back({1024 * 16, 1000});
m_mempoolConfig.push_back({1024 * 128, 200});
m_mempoolConfig.push_back({1024 * 512, 50});
m_mempoolConfig.push_back({1024 * 1024, 30});
m_mempoolConfig.push_back({1024 * 1024 * 4, 10});
return *this;
}
(1)获取当前roudi进程的所属组名,该组名即为运行该进程用户的所属组名
(2)内存池hard code的配置和以上roudi_config_example.toml中的设置一致
总结一下,roudi进程的配置文件包括其功能的个性化配置RouDiConfig类和SegmentConfig类,总的配置由IceoryxConfig类承载,IceoryxConfig类继承自RouDiConfig类和SegmentConfig类。SegmentConfig类中有一个关键的数据结构SegmentEntry:
struct SegmentConfig
{
struct SegmentEntry
{
SegmentEntry(const PosixGroup::groupName_t& readerGroup,
const PosixGroup::groupName_t& writerGroup,
const MePooConfig& memPoolConfig,
iox::mepoo::MemoryInfo memoryInfo = iox::mepoo::MemoryInfo()) noexcept
: m_readerGroup(readerGroup)
, m_writerGroup(writerGroup)
, m_mempoolConfig(memPoolConfig)
, m_memoryInfo(memoryInfo)
{
}
PosixGroup::groupName_t m_readerGroup;
PosixGroup::groupName_t m_writerGroup;
MePooConfig m_mempoolConfig;
iox::mepoo::MemoryInfo m_memoryInfo;
};
vector<SegmentEntry, MAX_SHM_SEGMENTS> m_sharedMemorySegments;
/// @brief Set Function for default values to be added in SegmentConfig
SegmentConfig& setDefaults() noexcept;
SegmentConfig& optimize() noexcept;
};
toml文件中的每个segment对应一个SegmentConfig,每个SegmentConfig包含需要访问这段内存的reader和writer group,以及内存池的进一步细分参数:
struct MePooConfig
{
public:
struct Entry
{
/// @brief set the size and count of memory chunks
Entry(uint64_t size, uint32_t chunkCount) noexcept
: m_size(size)
, m_chunkCount(chunkCount)
{
}
uint64_t m_size{0};
uint32_t m_chunkCount{0};
};
};
如果提供了toml文件,浏览其解析程序,发现还可以在segment配置writer、reader等字段:
for (auto segment : *segments)
{
auto writer = segment->get_as<std::string>("writer").value_or(into<std::string>(groupOfCurrentProcess));
auto reader = segment->get_as<std::string>("reader").value_or(into<std::string>(groupOfCurrentProcess));
iox::mepoo::MePooConfig mempoolConfig;
auto mempools = segment->get_table_array("mempool");
if (!mempools)
{
return iox::err(iox::roudi::RouDiConfigFileParseError::SEGMENT_WITHOUT_MEMPOOL);
}
if (mempools->get().size() > iox::MAX_NUMBER_OF_MEMPOOLS)
{
return iox::err(iox::roudi::RouDiConfigFileParseError::MAX_NUMBER_OF_MEMPOOLS_PER_SEGMENT_EXCEEDED);
}
for (auto mempool : *mempools)
{
auto chunkSize = mempool->get_as<uint64_t>("size");
auto chunkCount = mempool->get_as<uint32_t>("count");
if (!chunkSize)
{
return iox::err(iox::roudi::RouDiConfigFileParseError::MEMPOOL_WITHOUT_CHUNK_SIZE);
}
if (!chunkCount)
{
return iox::err(iox::roudi::RouDiConfigFileParseError::MEMPOOL_WITHOUT_CHUNK_COUNT);
}
mempoolConfig.addMemPool({*chunkSize, *chunkCount});
}
parsedConfig.m_sharedMemorySegments.push_back(
{PosixGroup::groupName_t(iox::TruncateToCapacity, reader.c_str(), reader.size()),
PosixGroup::groupName_t(iox::TruncateToCapacity, writer.c_str(), writer.size()),
mempoolConfig});
}
假如现在的登陆用户是vox,roudi进程运行后可以在/dev/shm看到建立的共享内存:
这个进程的用户和用户组都为vox,如果别的用户或用户组想访问这段共享内存,必须使用linux的acl机制添加相关权限,具体可以参考linux ACL权限控制之用户权限控制程序设计-优快云博客和linux ACL权限控制之组权限控制程序设计-优快云博客