In this article, we will explain the difference between the following code snippets:
1
classderived : public base
1
classderived : base
When defining a derived class in C++ that inherits from a base class, you have two different syntaxes to choose from: class derived : public base and class derived : base. These syntaxes determine the type of inheritance and have a significant impact on the accessibility of the base class members in the derived class.
It is customary to see the public specifier used while inheriting a base class.
Public Inheritance: class derived : public base
The syntax class derived : public base represents public inheritance. With public inheritance, the public members of the base class base retain their access levels in the derived class derived. Public members remain accessible as public members, protected members remain accessible as protected members, and private members remain inaccessible. This preserves the original access levels of the base class members.
Private Inheritance: class derived : base
The syntax class derived : base represents private inheritance by default. In private inheritance, both public and protected members of the base class base become private members in the derived class derived. Consequently, they are not directly accessible outside the class. Private members of the base class remain inaccessible as well.
In most scenarios, it is preferred to use the explicit public keyword (class derived : public base) to specify public inheritance. This ensures that the access levels of the base class members are preserved in the derived class, making the code more readable and intuitive.
By understanding the difference between these two inheritance types, you can make informed decisions when designing your class hierarchy in C++.
When working with the & operator in the C language, besides bitwise, its usage is limited to taking the address of a specified variable. However, in C++, the & operator serves two distinct purposes: taking the address of a variable and declaring a reference variable. To understand its purpose, it is crucial to consider the context in which the operator is used.
Taking the Address of a Variable
In C++, the & operator allows you to access the memory location where a variable is stored when used to take the address of a variable. Consider the following example:
1 2
int x = 42; int* px = &x; // & used to take the address of x
In this case, the & operator is employed to obtain the address of the variable x and assign it to the pointer variable px.
Declaring a Reference Variable
In addition to taking addresses, the & operator can also be used to declare a reference variable. A reference provides an alternative name or alias for an existing variable. Take a look at this example:
1 2
int x = 42; int& rx = x; // & used to declare the reference variable rx
Here, the & operator is utilized to declare the reference variable rx, which refers to the existing variable x. Any changes made to rx will directly affect the original variable x.
Capturing References in Lambdas
Now, let’s explore an example and determine whether it involves a reference or an address:
1 2 3 4 5 6
voidfunc(uint8_t data) { boost::asio::post(io, [&data]() { // Use the captured 'data' reference here }); }
Although my instinctive C brain would lean towards determining the address, in the realm of C++, this example demonstrates the concept of capturing references in lambdas. You can refer to the C++ reference documentation for more information.
Lambdas in C++ enable you to capture variables from the surrounding scope. By capturing variables by reference, you can avoid unnecessary data copies and improve performance. In this particular case, the & operator is employed to capture the variable data by reference in the lambda function passed to boost::asio::post. The captured reference to data remains valid throughout the execution of the lambda function.
To ensure the validity of captured references throughout the asynchronous operation, it is crucial to manage their lifespan and avoid accessing invalidated references.
Passing the Address of a Variable to a Lambda
Having covered the topic of capturing references in lambdas, the next question arises: how can we pass the address of a variable to a lambda?
To pass the address of a variable to a lambda, you can utilize the address-of operator & as shown in the following example:
1 2 3 4 5 6 7
voidfunc(uint8_t data) { boost::asio::post(io, [&data]() { // Use the address of 'data' here std::cout << "data address: " << static_cast<void*>(&data) << std::endl; }); }
In this example, the lambda captures the variable data by reference and uses the address-of operator & to obtain a pointer to data within the lambda body.
When declaring a member variable in a class, you have the option to declare it as a reference or as a non-reference. Understanding the difference between these two types is crucial, as it affects how they are initialized within the class.
Let’s dive into the dissimilarities between reference and non-reference members through an illustrative example:
intmain(){ int x = 42; MyClass my_class(x, 10); return0; }
In the given example, we define a class called MyClass featuring two member variables: ref_val_, a reference to an integer, and non_ref_val_, a regular integer.
The key disparity arises in the constructor. The ref_val_ member is initialized in the constructor’s initialization list using the argument ref_val, which is a reference to an integer. This step is essential since a reference must be initialized upon declaration.
One practical application of using reference members is when we want to associate an external object with the class without initializing it inside the class constructor. By initializing the reference member outside the class, we can control the lifetime and initialization of the referenced object separately.
However, it’s crucial to ensure that the lifetime of the referenced object extends beyond the lifetime of the object holding the reference. In this case, if x is going to be used in MyClass, you should ensure that x remains in scope for the entire duration of my_class‘s lifetime. Failure to do so may lead to undefined behavior.
Understanding the difference between reference and non-reference members helps you make informed decisions when designing your classes and ensures the proper usage and management of references within your codebase. It provides flexibility in managing object lifetimes and improves the overall design of your C++ classes.
The PCA954X is a family of I2C multiplexers/switches that allow you to control multiple I2C devices using a single I2C bus. In this section, we will explore how to manually switch between different channels of the PCA954X I2C mux.
Using the i2c-tools
The i2c-tools package provides a set of command-line tools for interacting with I2C devices in Linux. We will be using three main tools: i2cdetect, i2cdump, and i2cset. Make sure you have the i2c-tools package installed on your system before proceeding.
i2cdetect
The i2cdetect tool allows you to scan the I2C bus and detect devices connected to it. It provides a grid view with addresses and corresponding symbols indicating the presence of I2C devices. Here’s an example output:
The symbols used in the output have the following meanings:
“–”: The address was probed, but no chip responded.
“UU”: Probing was skipped because this address is currently in use by a driver, suggesting the presence of a chip.
Hexadecimal address: Indicates the presence of a chip at that address.
i2cdump
The i2cdump tool allows you to read and display the content of registers on an I2C device. You need to provide the bus number and the address of the device you want to dump. Here’s an example usage:
This output displays the content of the registers starting from address 0x00.
i2cset
The i2cset tool allows you to set the content of registers on an I2C device. You need to provide the bus number, the address of the device, and the data you want to write. Here’s an example usage:
1
root@albertlin:~# i2cset -y 4 0x70 0x00 0x04
In this example, we are writing the value 0x04 to register address 0x00 of the device at address 0x70 on bus 4.
PCA954X I2C Mux
The PCA954X is a family of I2C multiplexers/switches. You can find the datasheet for PCA9545A/B/C here.
Changing DTS through Yocto
If you’re using Yocto to build your Linux image, you can customize the Linux kernel configuration by following these steps:
Open a terminal and navigate to your Yocto build directory.
Run the following command to enter the Linux kernel menuconfig:
1
bitbake -c menuconfig virtual/kernel
Make the necessary changes in the menuconfig interface. Once you’re
done, save and exit.
Additionally, you can generate a defconfig file by running the following command:
1
bitbake -c savedefconfig virtual/kernel
Writing I2C Mux Device Tree Bindings
To configure the PCA954X I2C mux in the device tree, you can refer to the following documentation:
To add or remove I2C devices dynamically, you can use the new_device and delete_device attributes in the sysfs interface. Here’s an example:
1 2 3 4 5 6 7
root@albertlin:/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a300.i2c-bus/i2c-5/i2c-16/i2c-20# ls 20-0049 20-0056 delete_device i2c-dev mux_device name new_device of_node power subsystem uevent root@albertlin:/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a300.i2c-bus/i2c-5/i2c-16/i2c-20# echo "0x0049" > delete_device [ 424.372524] i2c i2c-20: delete_device: Deleting device tmp75 at 0x49 root@albertlin:/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a300.i2c-bus/i2c-5/i2c-16/i2c-20# echo "tmp75 0x49" > new_device [825617.903108] lm75 20-0049: hwmon2: sensor 'tmp75' [825617.908643] i2c i2c-20: new_device: Instantiated device tmp75 at 0x49
In the above example, delete_device is used to remove the device with address 0x49, and new_device is used to add a device named “tmp75” at address 0x49.
Reading Kernel Configuration at Runtime
To view the kernel configuration at runtime, you can use the following command:
1
zcat /proc/config.gz
This command will display the configuration options of the running kernel.
Switching PCA954X Channels
To manually switch the channel of an I2C mux, you can use the i2cset command with the appropriate parameters. Here’s an example:
1 2 3
i2cset -y 5 0x70 0x01 # Switch to channel 0 i2cset -y 5 0x70 0x02 # Switch to channel 1 i2cset -y 5 0x70 0x04 # Switch to channel 2
In the above commands, -y 5 specifies the I2C bus number (5 in this case), 0x70 is the address of the I2C mux device, and 0x01, 0x02, and 0x04 are the channel values representing channel 0, channel 1, and channel 2, respectively.
Here’s an example of how you can use these commands:
In the above example, we first switch to channel 0 by using i2cset -y 5 0x70 1, then we scan for devices on the I2C bus using i2cdetect -y 5. After that, we switch to channel 1 with i2cset -y 5 0x71 0 and perform another scan using i2cdetect -y 5. Finally, we switch to channel 2 with i2cset -y 5 0x71 2 and perform a scan again.
OpenBMC utilizes webui-vue as its front-end, which is a single-page application (SPA). Testing the web performance of SPAs can be challenging with traditional tools like Lighthouse, which are typically used for testing static websites. However, Sitespeed.io provides mechanisms to test SPAs, making it a suitable choice for testing the OpenBMC web pages.
Sitespeed.io
Sitespeed.io is a powerful tool that allows you to perform web performance testing on SPAs. It offers the ability to measure various performance metrics and provides detailed insights into the performance of your web application.
Writing a Testing Script
To demonstrate the performance testing of the OpenBMC webui-vue login process, we can refer to the example provided by Sitespeed.io for testing SPAs. You can find the example performance test script for Grafana here. Additionally, the Sitespeed.io documentation provides more information on scripting and available APIs, which you can find here.
For this article, let’s focus on testing the performance of the OpenBMC login process using the provided script openbmc.js:
// Start the measurement and give it the alias 'login' // The alias will be used when the metrics are sent to // Graphite/InfluxDB await commands.measure.start('login to dashboard');
// Find the submit button, click it, and wait for the // page load to complete on the next loaded URL await commands.click.byXpath('//*[@id="app"]/main/div/div[1]/div/form/button'); await commands.navigate('https://192.168.0.196/#/');
// Stop and collect the metrics return commands.measure.stop(); } catch (e) { // If the GUI changes and a link is not there, // the click commands will throw an error. // sitespeed.io will catch, log, and rethrow // and you can choose to handle the error } };
Make sure to adjust the URLs and element selectors in the script to match your OpenBMC setup.
With this script, you can measure the performance of the OpenBMC login process using Sitespeed.io. It captures metrics such as page load time, resource timings, and other performance-related data.
Running the Performance Test
To run the performance test on the OpenBMC web pages using the openbmc.js script, you can execute the following command:
In this command, the --spa flag indicates that the web page being tested is a single-page application, while the --multi flag enables multiple iterations of the test. The -n 1 argument determines the number of testing iterations to perform. Adjust the value of -n as per your requirements.
For more information on how to customize and extend the performance test, you can refer to the example provided by Sitespeed.io for testing SPAs here.
Video Recording
If you want to have a video recording of the script’s interaction during the performance test, you can specify the --browsertime.videoParams.debug flag. This flag generates a video in the test report, allowing you to visualize the user flow and interactions. Here’s an example command:
1
docker run --rm -v "$(pwd):/sitespeed.io" sitespeedio/sitespeed.io:27.3.0 openbmc.js --spa --multi --browsertime.videoParams.debug
Make sure to adjust the command and script parameters based on your specific requirements and OpenBMC setup.
By running these commands, you can perform performance testing on the OpenBMC web pages using Sitespeed.io, generate performance metrics, and optionally capture a video recording of the user interaction.
Viewing the Report
Once the performance testing commands have been executed, you can find the generated report in the sitespeed-result folder. The report is presented in the form of an index.html file, which serves as the entry point to access the performance results.
To view the report, navigate to the sitespeed-result folder and open the index.html file in a web browser. This will launch the report and provide you with detailed information about the performance of the OpenBMC web pages.
The report generated by Sitespeed.io includes various metrics such as page load times, resource sizes, network requests, and more. It offers insights into the performance characteristics of the web application, allowing you to identify areas for improvement and optimization.
Make sure to explore the different sections of the report to gain a comprehensive understanding of the performance test results.
Start by sourcing the setup script for your target hardware. You can find the script on the OpenBMC GitHub repository. Execute the following command in your terminal:
1
. setup romulus
Build the SDK
Next, build the Software Development Kit (SDK) using the bitbake command. Specify your target as an argument. For example:
The installation script will prompt you to enter the target directory for the SDK. Specify /usr/local/openbmc when prompted:
1
Enter target directory for SDK (default: /usr/local/oecore-x86_64): /usr/local/openbmc
Confirm the installation by typing ‘y’ when asked if you want to proceed:
1 2 3 4 5
You are about to install the SDK to "/usr/local/openbmc". Proceed [Y/n]? y Extracting SDK....................................................................................................................................done Setting it up...done SDK has been successfully set up and is ready to be used. Each time you wish to use the SDK in a new shell session, you need to source the environment setup script e.g.
The SDK will be extracted and set up in the specified directory.
Verify that the installation was successful by checking the contents of the installation directory:
1
albertlin@thinkbook:$ ls -al /usr/local/openbmc/
Using the SDK
Before compiling your code, you need to set up the SDK environment. Execute the following command to source the environment setup script:
Once the build is complete, you can find the compiled binary in the build directory. You can copy it to your OpenBMC target for execution using the scp command:
To execute the binary on your BMC target, follow these steps:
Connect to your BMC target using SSH.
1
ssh root@192.168.0.196
Navigate to the directory where you copied the binary.
1
root@romulus:/tmp# cd /tmp
Run the binary.
1 2
root@romulus:/tmp# ./hello Hello OpenBMC
That’s it! You have successfully cross-compiled and executed a program on OpenBMC.
Native Build
Source the setup script
Start by sourcing the setup script for your target hardware. You can find the script on the OpenBMC GitHub repository. Execute the following command in your terminal:
1
. setup romulus
Environment Setup and Checking
Check if the required packages listed by sdbusplus are installed:
albertlin@albertlin:boost_1_82_0$ ./bootstrap.sh --prefix=/usr Building B2 engine.. ... Further information:
- Command line help: ./b2 --help - Getting started guide: http://www.boost.org/more/getting_started/unix-variants.html - B2 documentation: http://www.boost.org/build/
albertlin@albertlin:boost_1_82_0$ sudo ./b2 --install Performing configuration checks ... The following directory should be added to linker library paths:
/home/albertlin/hypnoslin/openbmc/boost/boost_1_82_0/stage/lib albertlin@albertlin:sdbusplus$ cat /usr/include/boost/version.hpp | grep "BOOST_LIB_VERSION" // BOOST_LIB_VERSION must be defined to be the same as BOOST_VERSION #define BOOST_LIB_VERSION "1_82"
Check the Meson version used by OpenBMC:
1 2 3
albertlin@thinkbook:$ bitbake-layers show-recipes | grep -i meson -A 1 meson: meta 1.0.1
Ensure that the Meson version matches what OpenBMC currently uses. It’s recommended to have an upgraded version:
1 2 3 4 5 6 7 8 9 10 11 12 13
albertlin@albertlin:sdbusplus$ meson -v 0.61.2 albertlin@albertlin:sdbusplus$ python --version Python 3.10.6 albertlin@albertlin:sdbusplus$ python -m pip install meson Defaulting to user installation because normal site-packages is not writeable Collecting meson Downloading meson-1.1.1-py3-none-any.whl (918 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 918.4/918.4 KB 2.9 MB/s eta 0:00:00 Installing collected packages: meson Successfully installed meson-1.1.1 albertlin@thinkbook:~$ meson -v 1.1.1
If there are missing packages reported by Meson while building, install them:
albertlin@thinkbook:$ bitbake-layers show-recipes | grep gcc -A 1 gcc: meta 12.2.0 gcc-cross-arm: meta 12.2.0 gcc-cross-canadian-arm: meta 12.2.0 gcc-crosssdk-x86_64-oesdk-linux: meta 12.2.0 gcc-runtime: meta 12.2.0 gcc-sanitizers: meta 12.2.0 gcc-source-12.2.0: meta 12.2.0 – libgcc: meta 12.2.0 libgcc-initial: meta 12.2.0
1 2 3 4 5
albertlin@thinkbook:$ gcc -v Using built-in specs. COLLECT_GCC=gcc ... gcc version 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04.1)
Ensure that the g++ version matches what OpenBMC currently uses.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
albertlin@albertlin:~$ g++ -v Using built-in specs. COLLECT_GCC=g++ ... gcc version 9.5.0 (Ubuntu 9.5.0-1ubuntu1~22.04) albertlin@albertlin:~$ sudo apt install g++-12 Reading package lists... Done ... Processing triggers for man-db (2.10.2-1) ... albertlin@albertlin:~$ ls -al /usr/bin/g++ lrwxrwxrwx 1 root root 5 Mar 20 2020 /usr/bin/g++ -> g++-9 albertlin@albertlin:~$ sudo rm /usr/bin/g++ albertlin@albertlin:~$ sudo ln /usr/bin/g++-12 /usr/bin/g++ albertlin@albertlin:~$ g++ -v Using built-in specs. COLLECT_GCC=g++ ... gcc version 12.1.0 (Ubuntu 12.1.0-2ubuntu1~22.04)
Building and Executing
Clone the sdbusplus repository. Run the following command:
1 2
git clone https://github.com/openbmc/sdbusplus.git cd sdbusplus
Configure the build using Meson. Run the following command:
albertlin@albertlin:sdbusplus$ meson build -Dtests=disabled -Dexamples=disabled The Meson build system Version: 1.1.1 Source dir: /home/albertlin/hypnoslin/openbmc/sdbusplus Build dir: /home/albertlin/hypnoslin/openbmc/sdbusplus/build Build type: native build Project name: sdbusplus Project version: 1.0.0 C compiler for the host machine: cc (gcc 11.3.0 "cc (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0") C linker for the host machine: cc ld.bfd 2.38 C++ compiler for the host machine: c++ (gcc 12.1.0 "c++ (Ubuntu 12.1.0-2ubuntu1~22.04) 12.1.0") C++ linker for the host machine: c++ ld.bfd 2.38 Host machine cpu family: x86_64 Host machine cpu: x86_64 Found pkg-config: /usr/bin/pkg-config (0.29.2) Run-time dependency libsystemd found: YES 249 Program python3 (inflection, yaml, mako) found: YES (/usr/bin/python3) modules: inflection, yaml, mako Run-time dependency Boost found: YES 1.82.0 (/usr/include) Program sdbus++ found: YES (/home/albertlin/hypnoslin/openbmc/sdbusplus/tools/sdbus++) Program sdbus++ found: YES (overridden) Program sdbus++-gen-meson found: YES (/home/albertlin/hypnoslin/openbmc/sdbusplus/tools/sdbus++-gen-meson) Program sdbus++-gen-meson found: YES (overridden) Build targets in project: 1
sdbusplus 1.0.0
User defined options examples: disabled tests : disabled
Found ninja-1.10.1 at /usr/bin/ninja WARNING: Running the setup command as `meson [options]` instead of `meson setup [options]` is ambiguous and deprecated.
Build the project using Ninja. Run the following command: