Linux: Difference between revisions
(9 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
= Boot Procedure = | |||
# The primary bootloader, which is usually a fixed part of the hardware recognizes bootable media. | # The primary bootloader, which is usually a fixed part of the hardware recognizes bootable media. | ||
#* On PC compatible systems, [https://en.wikipedia.org/wiki/UEFI UEFI] or BIOS are the primary bootloaders. | #* On PC compatible systems, [https://en.wikipedia.org/wiki/UEFI UEFI] or BIOS are the primary bootloaders. | ||
Line 18: | Line 18: | ||
<br /> | <br /> | ||
== | = Device Trees = | ||
=== Systemd Unit Files | Device trees are files that contain a static, hierarchical description of the hardware components of a Linux system with Arm processor architecture. Device trees are usually read by the bootloader. The specification of the device tree file format can be found at [https://devicetree.org devicetree.org]. | ||
<br /> | |||
<br /> | |||
In a Linux kernel source repository, device tree source files are located in the ''linux/arch/arm/boot/dts'' directory. Device tree source files are also usually provided by manufacturers of hardware components that are connected via GPIO. | |||
<br /> | |||
Device tree definitions are written in text form into ''.dts'' or ''.dtsi'' files, compiled into binary ''.dtb'' files, and placed into the ''/boot'' directory of the Linux system. | |||
<br /> | |||
<br /> | |||
== DTS Syntax == | |||
<pre class="code"> | |||
/dts-v1/; | |||
#include "bcm2711.dtsi" | |||
#include "bcm2711-rpi.dtsi" | |||
/ { | |||
compatible = "raspberrypi,4-model-b", "brcm,bcm2711"; | |||
model = "Raspberry Pi 4 Model B"; | |||
chosen { | |||
/* 8250 auxiliary UART instead of pl011 */ | |||
stdout-path = "serial1:115200n8"; | |||
}; | |||
leds { | |||
led-act { | |||
gpios = <&gpio 42 GPIO_ACTIVE_HIGH>; | |||
}; | |||
led-pwr { | |||
label = "PWR"; | |||
gpios = <&expgpio 2 GPIO_ACTIVE_LOW>; | |||
default-state = "keep"; | |||
linux,default-trigger = "default-on"; | |||
}; | |||
}; | |||
... | |||
</pre> | |||
<br /> | |||
== Device Tree Compiler == | |||
<pre class="terminal"> | |||
sudo apt install device-tree-compiler | |||
dtc -I dts -O dtb -o my_system.dtb my_system.dts | |||
</pre> | |||
<br /> | |||
<br /> | |||
== Device Tree Overlays == | |||
Overlays are partial device tree definitions that modify an existing device tree file. The sources files have the extension ''.dts'', just like ordinary device tree source files. Compiled overlays have the '''.dtbo''' file extension and are placed in the '''/boot/overlays''' directory. | |||
<br /> | |||
<br /> | |||
Overlays are compiled by adding the ''-@'' flag, which generates symbols for referenced items (that the compiler would otherwise not be able to resolve). | |||
<pre class="terminal"> | |||
dtc -@ -I dts -O dtb -o camera_overlay.dtbo camera_overlay.dts | |||
</pre> | |||
<br /> | |||
= Systemd = | |||
== Systemd Unit Files == | |||
Unit files determine what kind of task is being started. The types of tasks are | Unit files determine what kind of task is being started. The types of tasks are | ||
* '''service''' for running daemons | * '''service''' for running daemons | ||
Line 32: | Line 89: | ||
<br /> | <br /> | ||
=== Unit Relationships (Dependencies) === | |||
There are various relationships between two units: | There are various relationships between two units: | ||
* '''requires''': The dependent unit must be loaded in order to load the current unit. | * '''requires''': The dependent unit must be loaded in order to load the current unit. | ||
Line 47: | Line 104: | ||
<br /> | <br /> | ||
=== Systemd Targets | == Systemd Services == | ||
Here is an example of using ''systemctl'' to query the status of the SSH server. | |||
<pre class="terminal"> | |||
systemctl status sshd | |||
</pre> | |||
<br /> | |||
To start or stop the server, you would type | |||
<pre class="terminal"> | |||
sudo systemctl start sshd | |||
sudo systemctl stop sshd | |||
</pre> | |||
<br /> | |||
To configure a service, edit its configuration file located in '''/etc/systemd/system'''. | |||
<br /> | |||
<br /> | |||
== Systemd Targets == | |||
Targets are groups of unit files. If a target includes the statement <span class="code">AllowIsolate = yes</span> the target becomes an initialization endpoint at which systemd stops executing other units. Isolate targets are therefore similar to System V runlevels that were used for initialization before systemd. | Targets are groups of unit files. If a target includes the statement <span class="code">AllowIsolate = yes</span> the target becomes an initialization endpoint at which systemd stops executing other units. Isolate targets are therefore similar to System V runlevels that were used for initialization before systemd. | ||
<br /> | <br /> | ||
Line 54: | Line 127: | ||
<br /> | <br /> | ||
<br /> | <br /> | ||
== Systemd Analysis == | |||
Systemd can plot a chart that shows how the units are launched over time. | Systemd can plot a chart that shows how the units are launched over time. | ||
<pre class="terminal"> | <pre class="terminal"> | ||
Line 66: | Line 139: | ||
<br /> | <br /> | ||
= Which Linux distro and version am I running? = | |||
<pre class="terminal"> | <pre class="terminal"> | ||
cat /etc/os-release | cat /etc/os-release | ||
Line 72: | Line 145: | ||
<br /> | <br /> | ||
= Dynamic Shared Objects (DSO) = | |||
Ulrich Drepper's [https://www.akkadia.org/drepper/dsohowto.pdf How To Write Shared Libraries] is a good resource for learning more about the ELF format, about optimizing your shared objects, and how to control the visibility of program symbols in the generated binaries. | Ulrich Drepper's [https://www.akkadia.org/drepper/dsohowto.pdf How To Write Shared Libraries] is a good resource for learning more about the ELF format, about optimizing your shared objects, and how to control the visibility of program symbols in the generated binaries. | ||
<br /> | <br /> | ||
<br /> | <br /> | ||
= Tracing system calls with ''strace'' = | |||
'''strace''' is a command that lets you trace the system calls made by the command that is passed as the argument. For example, | '''strace''' is a command that lets you trace the system calls made by the command that is passed as the argument. For example, | ||
Line 171: | Line 244: | ||
sudo apt install ltrace | sudo apt install ltrace | ||
</pre> | </pre> | ||
<br /> | <br /> | ||
Line 189: | Line 261: | ||
<br /> | <br /> | ||
= Interprocess Communication Mechanisms = | |||
== Shared Memory == | |||
Linux offers creating System V shared memory segments, which can be used for interprocess communication. | Linux offers creating System V shared memory segments, which can be used for interprocess communication. | ||
<pre class="code"> | <pre class="code"> | ||
Line 225: | Line 297: | ||
<br /> | <br /> | ||
== Memory Mapped Files == | |||
Linux offers mapping files to memory regions, which enables working on the file contents much more efficiently, as if it is a memory region. For example, pointers can be used in a C/C++ program to edit the contents. The memory mapped region can be shared between processes. This allows the processes to operate on large files, for example in a writer-subscriber architecture, while preserving memory. Unlike the shared memory method described above, the contents of the shared memory region are written to a file and thus persist between sessions and even system reboots. | Linux offers mapping files to memory regions, which enables working on the file contents much more efficiently, as if it is a memory region. For example, pointers can be used in a C/C++ program to edit the contents. The memory mapped region can be shared between processes. This allows the processes to operate on large files, for example in a writer-subscriber architecture, while preserving memory. Unlike the shared memory method described above, the contents of the shared memory region are written to a file and thus persist between sessions and even system reboots. | ||
<pre class="code"> | <pre class="code"> | ||
Line 272: | Line 344: | ||
<br /> | <br /> | ||
== Pipes == | |||
Pipes are another IPC mechanism where one process can send messages to another. | Pipes are another IPC mechanism where one process can send messages to another. | ||
<pre class="code"> | <pre class="code"> | ||
Line 310: | Line 382: | ||
<br /> | <br /> | ||
== | = D-Bus and dmesg = | ||
<br /> | |||
= GUI-less Mode = | |||
== Switching to GUI-less (Multi-User) Mode with Systemd == | |||
On a Linux system that uses systemd, we can control whether the graphical user interface is loaded and shown by specifying a suitable systemd target. To turn off the graphical desktop immediately, enter | |||
<pre class="terminal"> | |||
sudo systemctl isolate multi-user.target | |||
</pre> | |||
To prevent the GUI from being loaded during the system startup, type | |||
<pre class="terminal"> | |||
sudo systemctl set-default multi-user.target | |||
</pre> | |||
To return to the graphical desktop, type | |||
<br/> | |||
<pre class="terminal"> | |||
sudo systemctl isolate graphical.target | |||
</pre> | |||
and to make the graphical desktop the default again, type | |||
<pre class="terminal"> | |||
sudo systemctl set-default graphical.target | |||
</pre> | |||
<br /> | |||
== Setting the Screen Resolution for GUI-less Mode == | |||
* Restart the machine to the Grub boot menu. | |||
* Press 'c' to get into the Grub command console. | |||
* Run the command 'vbeinfo' to list possible resolutions. | |||
* Note down the desired resolution. For example, ''1024x768x32'', where ''32'' is the color mode. | |||
* Type ''exit'' to return to the Grub boot menu and boot into Ubuntu. | |||
* Open '''/etc/default/grub''' in an editor to add the line | |||
<pre class="code"> | |||
GRUB_GFXPAYLOAD_LINUX=1024x768x32 | |||
</pre> | |||
* Optionally, add the line | |||
<pre class="code"> | |||
GRUB_GFXMODE=1024x768 | |||
</pre> | |||
to set the screen resolution for the GRUB boot menu itself. | |||
* Finally, run the Grub updater | |||
<pre class="terminal"> | |||
sudo update-grub | |||
</pre> | |||
<br /> | |||
= Message of the Day (MOTD) = | |||
For users that open simultaneous terminal sessions to different Linux machines, it is helpful to see a distinct ''message of the day'' (''MOTD'') on each terminal. The MOTD can be customized by editing the shell scripts located in '''/etc/update-motd.d/'''. The shell scripts are executed in alphabetical order, their output is the MOTD. The names of the script files start with a number between 1 and 99, which makes ordering of the scripts immediately visible. Script files can be deleted or their execution bit flipped via ''chmod'' in order to disable the corresponding output. New script files can be added, and their file names chosen according to the desired position of their output. | |||
<br /> | |||
<br /> | |||
Additionally, it is possible to create the '''/etc/motd''' file with static content, whose output will be appended to the dynamically generated MOTD. With ''/etc/motd'', the MOTD can be embellished with ASCII art, like the Robo.Fish logo below | |||
<br /> | |||
<br /> | |||
<pre class="code"> | |||
.';cldxxkkkxxdolc;,.. | |||
.;oOXWWMMMMMMMMMMMMMMWWXKOxo:,. | |||
'o0WMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0xl:'. | |||
.;xXMMMMMMMMMMMMMMMMMMMMMMMMMWWWNNXXKKK0kd:. | |||
.:ONMMMMMMMMMMMMMMMMMMWXKOxdoc:;;,'.......... | |||
. 'o0WMMMMMMMMMMMMMMWN0xl:,.. | |||
Xx:. .:kXWMMMMMMMMMMMMMN0d:'. | |||
MMWKxc'. .'cxXWMMMMMMMMMMMMNOo:,. | |||
MMMMMMN0xc,. .,cd0NWMMMMMMMMMMMMN0o,. | |||
MMMMMMWWNX0d:;ckKNWMMMMMMMMMMWN0xl,. | |||
lllcc:;,'.. ..,;:clloollc:,.. | |||
</pre> | |||
<br /> | |||
The MOTD can be previewed without needing to start a new shell session: | |||
<pre class="terminal"> | |||
sudo run-parts /etc/update-motd.d/ | |||
</pre> | |||
<br /> | |||
= SSH Logins Without Typing The Password = | |||
If you connect to a remote machine via ''ssh'' or copy files via ''scp'' frequently, you should consider transferring your public key to the remote machine and using ''ssh-agent'' locally in order to log in without typing your password. | |||
<br /> | |||
== Generating An SSH Key == | |||
First, you create an RSA public/private key pair that identifies your local machine. You may already have created such a key in your SSH keychain. It is good security practice, however, to use a separate key for each device you want to connect to. Let us assume you want to connect to a [[Raspberry Pi]]. The command to create a new SSH key would be | |||
<pre class="terminal"> | |||
ssh-keygen -t rsa -b 2048 -C "for Raspberry Pi" | |||
</pre> | |||
where '-b 2048' specifies the key length in bits. You will be prompted to enter the path and name of the file in which the key will be saved. Choose a name that makes it clear that it was created for connecting to the Raspberry Pi. Next, you will be prompted for a password. Just press the Enter key for no password. | |||
<br /> | |||
== Transferring Your Public SSH Key == | |||
Now that the SSH key is created, you need to transfer the generated public(!) key to the remote machine. If the remote machine will ever only be accessed with a single SSH identity, you can simply copy the user's public key into the ''~/.ssh'' folder on the remove machine. | |||
<pre class="terminal"> | |||
scp ~/.ssh/id_rsa.pub pi@192.168.1.10:/home/pi/.ssh/authorized_keys | |||
</pre> | |||
where the copied file is renamed to '''authorized_keys''', which is the expected file name for the SSH server to look up the public keys of trusted clients. | |||
<br /> | |||
<br /> | |||
Generally, however, you should use '''ssh-copy-id''' to transfer the SSH identity. This tool appends the SSH identity to the remote ''~/.ssh/authorized_keys'' instead of overwriting it. | |||
<pre class="terminal"> | |||
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@192.168.1.10 | |||
</pre> | |||
<br /> | |||
== Launching ssh-agent == | |||
Use ''ssh-agent'' on your local machine to start a special shell session that uses the new SSH key to automatically authenticate any SSH connection from your machine to the Raspberry Pi: | |||
<pre class="terminal"> | |||
eval "$(ssh-agent -s)" # starting a new shell session | |||
ssh-add ~/.ssh/for-raspberry-pi_rsa | |||
</pre> | |||
<br /> | |||
Done! This was the last time you had to enter the password for the remote user. | |||
<br /> | |||
<br /> | |||
By the way, you can list all the keys that were added to the SSH agent by entering | |||
<pre class="terminal"> | |||
ssh-add -l | |||
</pre> | |||
<br /> | |||
= Bluetooth = | |||
On most Linux systems, Bluetooth functionality is provided by the kernel module, libraries and utilities of the [http://www.bluez.org BlueZ] project. So, make sure that BlueZ is installed. | |||
<pre class="terminal"> | |||
sudo apt install bluez | |||
</pre> | |||
<br /> | |||
== Bluetooth Low Energy (BLE) Control in Terminal == | |||
Set the Bluetooth controller to operate in BLE mode only by editing '''/etc/bluetooth/main.conf''' to make sure that the value of ''ControllerMode'' is set to ''le''. | |||
<pre class="code"> | |||
ControllerMode = le | |||
</pre> | |||
If a change had to be made, you need to reboot the system. | |||
<br /> | |||
<br /> | |||
Now issue the command | |||
<pre class="terminal"> | |||
sudo systemctl start bluetooth | |||
</pre> | |||
to start the ''bluetooth'' service. You can also stop, restart and query the status with by replacing ''start'' with ''stop'', ''restart'', ''status'', respectively. | |||
<br /> | |||
<br /> | |||
With the bluetooth service running, enter the command | |||
<pre class="terminal"> | |||
bluetoothctl | |||
</pre> | |||
which runs a Bluetooth control program in the terminal. In this program you can, for example, display the list of commands, query the controller, power on the controller, scan for devices with RSSI 80 or better, advertise with the local name ''RPi3'' and, finally, power off and quit the program with the following commands: | |||
<pre class="terminal"> | |||
help | |||
show | |||
power on | |||
menu scan | |||
rssi 80 | |||
back | |||
scan on | |||
... | |||
scan off | |||
discoverable on | |||
advertise on | |||
menu advertise | |||
name RPi3 | |||
back | |||
... | |||
advertise off | |||
power off | |||
quit | |||
</pre> | |||
== Bluetooth Programming with D-Bus == | |||
Here is an (incomplete!) example of programming D-Bus in C++ using the [https://github.com/Kistler-Group/sdbus-cpp/blob/master/docs/using-sdbus-c++.md sdbus-c++] C++ binding, for which you need to install ''libsdbus-c++-dev''. | |||
<pre class="terminal"> | |||
sudo apt install libbluetooth-dev libsdbus-c++-dev | |||
</pre> | |||
<br /> | <br /> | ||
= | Inspired by [https://punchthrough.com/creating-a-ble-peripheral-with-bluez/ this article from PunchThrough's Andy Lee], I have created a basic BLE peripheral. | ||
<pre class="code"> | |||
#include <sdbus-c++/sdbus-c++.h> | |||
#include <iostream> | |||
/** | |||
* Names of D-Bus system services can be looked up with the terminal command | |||
* | |||
* busctl list | |||
* | |||
* We see that the name of the D-Bus system service of the bluetooth daemon is 'org.bluez'. | |||
* The D-Bus object path for a given system service can be queried with the terminal command | |||
* | |||
* busctl tree <service name> | |||
* | |||
* For 'org.bluez' let us assume the object path is '/org/bluez/hci0'. | |||
* We can now introspect the interfaces available in the D-Bus object via | |||
* | |||
* busctl introspect org.bluez /org/bluez/hci0 | |||
* | |||
* or | |||
* | |||
* gdbus introspect -y -d "org.bluez" -o "/org/bluez/hci0" | |||
* | |||
* We will use to the methods and properties in the published interfaces | |||
* to create a BLE peripheral which advertises a service named 'Test'. | |||
* Additional information on the methods and their parameters can be looked up from | |||
* the BlueZ documentation at https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc | |||
*/ | |||
int main(int numargs, char* args[]) | |||
{ | |||
const char* bluezServiceName = "org.bluez"; | |||
const char* bluezObjectPath = "/org/bluez/hci0"; | |||
const char* interface_adapter = "org.bluez.Adapter1"; | |||
const char* interface_advertisingManager = "org.bluez.LEAdvertisingManager1"; | |||
auto bluezProxy = sdbus::createProxy(bluezServiceName, bluezObjectPath); | |||
if (bluezProxy == nullptr) | |||
{ | |||
std::cerr << "Error while creating proxy to BlueZ D-Bus service." << std::endl; | |||
return EXIT_FAILURE; | |||
} | |||
bluezProxy->setProperty("Powered").onInterface(interface_adapter).toValue(true); | |||
bluezProxy->setProperty("Alias").onInterface(interface_adapter).toValue("Test"); | |||
// TO BE DONE | |||
bluezProxy->finishRegistration(); | |||
std::cout << "BLE Peripheral Example" << std::endl; | |||
return EXIT_SUCCESS; | |||
} | |||
</pre> | |||
<br /> | <br /> | ||
== | build with | ||
=== Platform detection | <pre class="terminal"> | ||
my_arch=`uname -i` | |||
g++ -o ble-peripheral main.cpp -L/usr/lib/${my_arch}-linux-gnu -lsdbus-c++ | |||
</pre> | |||
<br /> | |||
= Video4Linux = | |||
Video4Linux is the video pipeline control system in Linux. Start by installing the required packages. On a Debian distribution, type | |||
<pre class="terminal"> | |||
sudo apt install libv4l-0 v4l-utils v4l-conf libv4lconvert0 | |||
v4l2-ctl --list-devices | |||
</pre> | |||
<br /> | |||
= C++ programming notes = | |||
== Platform detection == | |||
To detect whether code is compiled for the Linux platform you can check if ''__linux__'' is defined. | To detect whether code is compiled for the Linux platform you can check if ''__linux__'' is defined. | ||
<pre class="code"> | <pre class="code"> |
Latest revision as of 2023-01-17T12:04:32
Boot Procedure
- The primary bootloader, which is usually a fixed part of the hardware recognizes bootable media.
- On PC compatible systems, UEFI or BIOS are the primary bootloaders.
- On BIOS systems, the Master Boot Record (MBR) area of a connected storage device is searched for a boot executable. The MBR is a special area, separate from the actual data storage area. BIOS is an old system, dating back to the early 1980s, but not yet obsolete.
- On a UEFI system, which supports storage media with much larger capacity, the GPT partition table is used to determine if the storage device provides an EFI System Partition containing the folder /efi with a subfolder that contains the secondary boot loader. /efi/microsoft or /efi/grub, for example. After a Linux system is booted by UEFI hardware, you can open a terminal and inspect the partition table by typing sudo gdisk /dev/sda.
- If a secondary bootloader is found on the bootable media, it is loaded.
- On PC compatibles the secondary bootloader is usually GRUB2.
- On Arm Cortex-A based systems, this is often U-Boot.
- Some systems have proprietary bootloaders. Raspberry Pi, for example.
- To support the bootloader, a standard interface called LBA (linear block access) exists for storage media. This interface is not optimized for the capabilities of the storage device but is sufficient for the bootloader to search for a bootable operating system, which will later load the appropriate driver for the storage media.
- The secondary bootloader loads the Linux kernel.
- In the case of GRUB2, the boot media is searched for the boot partition using LBA mode, stage 2 is loaded from a separate data area of the boot media, the module for working with the boot partitions filesystem is loaded, the GRUB config file (/boot/grub/grub.cfg) is loaded from the boot partition, the user is optionally shown a GRUB menu, the kernel is loaded with the user parameters.
- The kernel optionally loads an initial root file system into memory (often initramfs), which includes the device drivers specific for the target hardware. For example, display drivers for showing graphical output during the boot sequence.
- Technically, all hardware-specific device drivers can be compiled into the kernel itself. In order to keep the kernel generic, however, and to avoid mixing components with incompatible distribution licenses (the GPL 2.0 license used by Linux prevents distributing a kernel with built-in proprietary drivers), certain drivers are placed into initramfs, from where they are loaded when needed.
- After the hardware is initialized, the kernel switches from initramfs to the real filesystem.
- The kernel starts the first process, the init process. The init process starts all the system processes (a.k.a. services) configured to run when the system launches.
- init used to be a shell script located in the filesystem at /sbin/init. Nowadays, many Linux distributions use the systemd initialization method, where a binary executable is called instead of a shell script.
Device Trees
Device trees are files that contain a static, hierarchical description of the hardware components of a Linux system with Arm processor architecture. Device trees are usually read by the bootloader. The specification of the device tree file format can be found at devicetree.org.
In a Linux kernel source repository, device tree source files are located in the linux/arch/arm/boot/dts directory. Device tree source files are also usually provided by manufacturers of hardware components that are connected via GPIO.
Device tree definitions are written in text form into .dts or .dtsi files, compiled into binary .dtb files, and placed into the /boot directory of the Linux system.
DTS Syntax
/dts-v1/; #include "bcm2711.dtsi" #include "bcm2711-rpi.dtsi" / { compatible = "raspberrypi,4-model-b", "brcm,bcm2711"; model = "Raspberry Pi 4 Model B"; chosen { /* 8250 auxiliary UART instead of pl011 */ stdout-path = "serial1:115200n8"; }; leds { led-act { gpios = <&gpio 42 GPIO_ACTIVE_HIGH>; }; led-pwr { label = "PWR"; gpios = <&expgpio 2 GPIO_ACTIVE_LOW>; default-state = "keep"; linux,default-trigger = "default-on"; }; }; ...
Device Tree Compiler
sudo apt install device-tree-compiler dtc -I dts -O dtb -o my_system.dtb my_system.dts
Device Tree Overlays
Overlays are partial device tree definitions that modify an existing device tree file. The sources files have the extension .dts, just like ordinary device tree source files. Compiled overlays have the .dtbo file extension and are placed in the /boot/overlays directory.
Overlays are compiled by adding the -@ flag, which generates symbols for referenced items (that the compiler would otherwise not be able to resolve).
dtc -@ -I dts -O dtb -o camera_overlay.dtbo camera_overlay.dts
Systemd
Systemd Unit Files
Unit files determine what kind of task is being started. The types of tasks are
- service for running daemons
- mount for mounting storage media, etc
- automount for mounting directories when needed
- timer for starting jobs at specific times
- target (a group of unit files, or a state that the machine should be in after booting)
- path (monitoring filesystem events)
The original unit files that are installed by the package manager are placed in /usr/lib/systemd/system and should not be modified.
If modifications to unit files are to be made, the modified unit files should be placed into /etc/systemd/system.
Unit Relationships (Dependencies)
There are various relationships between two units:
- requires: The dependent unit must be loaded in order to load the current unit.
- wants: The dependent unit is not critical
- requisite: the dependent unit must be loaded and already active.
- conflicts: the unit must not be loaded for the current to be loaded
- before: the current unit must be activated before the given units
- after: the current unit must be activated after the given units
The dependency graph can be displayed on the terminal via
sudo systemctl list-dependencies
Systemd Services
Here is an example of using systemctl to query the status of the SSH server.
systemctl status sshd
To start or stop the server, you would type
sudo systemctl start sshd sudo systemctl stop sshd
To configure a service, edit its configuration file located in /etc/systemd/system.
Systemd Targets
Targets are groups of unit files. If a target includes the statement AllowIsolate = yes the target becomes an initialization endpoint at which systemd stops executing other units. Isolate targets are therefore similar to System V runlevels that were used for initialization before systemd.
For a custom target (or service), the relationship to the unit files that are WantedBy or RequiredBy the target (or service) will be established by systemd through symbolic links to the wanted or required unit files. These symbolic links are placed in /etc/systemd/system, within a subdirectory whose name starts with the name of the unit file and ends with the name of the type of relationship.
Systemd Analysis
Systemd can plot a chart that shows how the units are launched over time.
systemd-analyze plot > analysis.html
generates an HTML page with a chart like this
Which Linux distro and version am I running?
cat /etc/os-release
Ulrich Drepper's How To Write Shared Libraries is a good resource for learning more about the ELF format, about optimizing your shared objects, and how to control the visibility of program symbols in the generated binaries.
Tracing system calls with strace
strace is a command that lets you trace the system calls made by the command that is passed as the argument. For example,
strace echo "hello"
prints
execve("/usr/bin/echo", ["echo", "hello"], 0x7ffefa8f4928 /* 42 vars */) = 0 brk(NULL) = 0x561cf6532000 arch_prctl(0x3001 /* ARCH_??? */, 0x7fff61293910) = -1 EINVAL (Invalid argument) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7faf85c44000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=53267, ...}, AT_EMPTY_PATH) = 0 mmap(NULL, 53267, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7faf85c36000 close(3) = 0 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48 pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0i8\235HZ\227\223\333\350s\360\352,\223\340."..., 68, 896) = 68 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2216304, ...}, AT_EMPTY_PATH) = 0 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 mmap(NULL, 2260560, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7faf85a0e000 mmap(0x7faf85a36000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7faf85a36000 mmap(0x7faf85bcb000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7faf85bcb000 mmap(0x7faf85c23000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x214000) = 0x7faf85c23000 mmap(0x7faf85c29000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7faf85c29000 close(3) = 0 mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7faf85a0b000 arch_prctl(ARCH_SET_FS, 0x7faf85a0b740) = 0 set_tid_address(0x7faf85a0ba10) = 1874 set_robust_list(0x7faf85a0ba20, 24) = 0 rseq(0x7faf85a0c0e0, 0x20, 0, 0x53053053) = 0 mprotect(0x7faf85c23000, 16384, PROT_READ) = 0 mprotect(0x561cf5067000, 4096, PROT_READ) = 0 mprotect(0x7faf85c7e000, 8192, PROT_READ) = 0 prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0 munmap(0x7faf85c36000, 53267) = 0 getrandom("\x01\x62\x8d\xf4\xec\xea\xd8\x4e", 8, GRND_NONBLOCK) = 8 brk(NULL) = 0x561cf6532000 brk(0x561cf6553000) = 0x561cf6553000 openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3 newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=6070224, ...}, AT_EMPTY_PATH) = 0 mmap(NULL, 6070224, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7faf85441000 close(3) = 0 newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}, AT_EMPTY_PATH) = 0 write(1, "hello\n", 6) = 6 close(1) = 0 close(2) = 0 exit_group(0) = ? +++ exited with 0 +++
lists system calls made by echo as they are made.
Instead of printing system calls as they are made, strace can print a short summary of which system calls were how many times and the time spent.
strace -c echo "hello"
prints
hello % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0,00 0,000000 0 1 read 0,00 0,000000 0 1 write 0,00 0,000000 0 5 close 0,00 0,000000 0 9 mmap 0,00 0,000000 0 3 mprotect 0,00 0,000000 0 1 munmap 0,00 0,000000 0 3 brk 0,00 0,000000 0 4 pread64 0,00 0,000000 0 1 1 access 0,00 0,000000 0 1 execve 0,00 0,000000 0 2 1 arch_prctl 0,00 0,000000 0 1 set_tid_address 0,00 0,000000 0 3 openat 0,00 0,000000 0 4 newfstatat 0,00 0,000000 0 1 set_robust_list 0,00 0,000000 0 1 prlimit64 0,00 0,000000 0 1 getrandom 0,00 0,000000 0 1 rseq ------ ----------- ----------- --------- --------- ---------------- 100,00 0,000000 0 43 2 total
Similarly, ltrace will list the calls to library functions made by the command that was passed as the argument to ltrace.
sudo apt install ltrace
Signals
Signals are software interrupts that the the kernel can send an executable. The executable may react to the signal or choose to ignore it. The two signals SIGKILL and SIGTERM, however, can not be ignored by executable and must be handled.
An overview of the signals can be found in the man pages entry about signals
man 7 signal
Some command-line tools do useful stuff when certain signals are sent to them. For example, the dd tool prints the progress of the copying task when the SIGUSR1 signal is sent to it via the kill command.
dd if=${HOME}/image.iso of=/dev/sdb & kill -s USR1 $(pidof dd)
Interprocess Communication Mechanisms
Linux offers creating System V shared memory segments, which can be used for interprocess communication.
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> // creating an IPC key key_t segmentKey = ftok(”path_to_some_existing_file", 'R') if (segmentKey == -1) { perror("could not create IPC key"); exit(1); } // accessing the shared memory segment, creating it if it doesn't exist int sharedMemoryID = shmget(segmentKey, SHM_SIZE, 0644 | IPC_CREAT) if (sharedMemoryID == -1) { perror("could not access (or create) the shared memory segment"); exit(1); } // attaching a pointer void* memoryPtr = shmat(sharedMemoryID, NULL, 0); if (*((char*)memoryPtr) == -1) { perror("could not attach a pointer to the shared memory segment"); exit(1); }
To learn more about shared memory segments, type
man ftok man shmget man shmat
Memory Mapped Files
Linux offers mapping files to memory regions, which enables working on the file contents much more efficiently, as if it is a memory region. For example, pointers can be used in a C/C++ program to edit the contents. The memory mapped region can be shared between processes. This allows the processes to operate on large files, for example in a writer-subscriber architecture, while preserving memory. Unlike the shared memory method described above, the contents of the shared memory region are written to a file and thus persist between sessions and even system reboots.
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> static const int MAX_INPUT_LENGTH = 50; int main(int argc, char** argv) { int fd = open(argv[1], O_RDWR | O_CREAT); char* shared_mem = mmap(NULL, MAX_INPUT_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); close(fd); if (!strcmp(argv[2], "read")) { // continously listens to text messages from the 'write' process and writes to the terminal while (1) { shared_mem[MAX_INPUT_LENGTH-1] = '\0'; printf("%s", shared_mem); sleep(1); } } else if (!strcmp(argv[2], "write")) { // continuously sends the input from the terminal to the 'read' process while (1) { fgets(shared_mem, MAX_INPUT_LENGTH, stdin); } } else { printf("Unsupported command\n"); } }
To learn more about memory mapped files, refer to the man page
man mmap
Pipes
Pipes are another IPC mechanism where one process can send messages to another.
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <unistd.h> int main(void) { int pipeDescriptor[2]; pipe(pipeDescriptor); // writing to pipeDescriptor[0], reading from pipeDescriptor[1] if (!fork()) { printf(" CHILD: writing to pipe and exiting\n"); write(pipeDescriptor[1], "test", 5); } else { char buffer[5]; printf("PARENT: reading from pipe\n"); read(pipeDescriptor[0], buffer, 5); printf("PARENT: read \"%s\"\n", buf); wait(NULL); } return 0; }
To learn more about pipes, refer to the man page
man pipe
D-Bus and dmesg
GUI-less Mode
Switching to GUI-less (Multi-User) Mode with Systemd
On a Linux system that uses systemd, we can control whether the graphical user interface is loaded and shown by specifying a suitable systemd target. To turn off the graphical desktop immediately, enter
sudo systemctl isolate multi-user.target
To prevent the GUI from being loaded during the system startup, type
sudo systemctl set-default multi-user.target
To return to the graphical desktop, type
sudo systemctl isolate graphical.target
and to make the graphical desktop the default again, type
sudo systemctl set-default graphical.target
Setting the Screen Resolution for GUI-less Mode
- Restart the machine to the Grub boot menu.
- Press 'c' to get into the Grub command console.
- Run the command 'vbeinfo' to list possible resolutions.
- Note down the desired resolution. For example, 1024x768x32, where 32 is the color mode.
- Type exit to return to the Grub boot menu and boot into Ubuntu.
- Open /etc/default/grub in an editor to add the line
GRUB_GFXPAYLOAD_LINUX=1024x768x32
- Optionally, add the line
GRUB_GFXMODE=1024x768
to set the screen resolution for the GRUB boot menu itself.
- Finally, run the Grub updater
sudo update-grub
Message of the Day (MOTD)
For users that open simultaneous terminal sessions to different Linux machines, it is helpful to see a distinct message of the day (MOTD) on each terminal. The MOTD can be customized by editing the shell scripts located in /etc/update-motd.d/. The shell scripts are executed in alphabetical order, their output is the MOTD. The names of the script files start with a number between 1 and 99, which makes ordering of the scripts immediately visible. Script files can be deleted or their execution bit flipped via chmod in order to disable the corresponding output. New script files can be added, and their file names chosen according to the desired position of their output.
Additionally, it is possible to create the /etc/motd file with static content, whose output will be appended to the dynamically generated MOTD. With /etc/motd, the MOTD can be embellished with ASCII art, like the Robo.Fish logo below
.';cldxxkkkxxdolc;,.. .;oOXWWMMMMMMMMMMMMMMWWXKOxo:,. 'o0WMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0xl:'. .;xXMMMMMMMMMMMMMMMMMMMMMMMMMWWWNNXXKKK0kd:. .:ONMMMMMMMMMMMMMMMMMMWXKOxdoc:;;,'.......... . 'o0WMMMMMMMMMMMMMMWN0xl:,.. Xx:. .:kXWMMMMMMMMMMMMMN0d:'. MMWKxc'. .'cxXWMMMMMMMMMMMMNOo:,. MMMMMMN0xc,. .,cd0NWMMMMMMMMMMMMN0o,. MMMMMMWWNX0d:;ckKNWMMMMMMMMMMWN0xl,. lllcc:;,'.. ..,;:clloollc:,..
The MOTD can be previewed without needing to start a new shell session:
sudo run-parts /etc/update-motd.d/
SSH Logins Without Typing The Password
If you connect to a remote machine via ssh or copy files via scp frequently, you should consider transferring your public key to the remote machine and using ssh-agent locally in order to log in without typing your password.
Generating An SSH Key
First, you create an RSA public/private key pair that identifies your local machine. You may already have created such a key in your SSH keychain. It is good security practice, however, to use a separate key for each device you want to connect to. Let us assume you want to connect to a Raspberry Pi. The command to create a new SSH key would be
ssh-keygen -t rsa -b 2048 -C "for Raspberry Pi"
where '-b 2048' specifies the key length in bits. You will be prompted to enter the path and name of the file in which the key will be saved. Choose a name that makes it clear that it was created for connecting to the Raspberry Pi. Next, you will be prompted for a password. Just press the Enter key for no password.
Transferring Your Public SSH Key
Now that the SSH key is created, you need to transfer the generated public(!) key to the remote machine. If the remote machine will ever only be accessed with a single SSH identity, you can simply copy the user's public key into the ~/.ssh folder on the remove machine.
scp ~/.ssh/id_rsa.pub pi@192.168.1.10:/home/pi/.ssh/authorized_keys
where the copied file is renamed to authorized_keys, which is the expected file name for the SSH server to look up the public keys of trusted clients.
Generally, however, you should use ssh-copy-id to transfer the SSH identity. This tool appends the SSH identity to the remote ~/.ssh/authorized_keys instead of overwriting it.
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@192.168.1.10
Launching ssh-agent
Use ssh-agent on your local machine to start a special shell session that uses the new SSH key to automatically authenticate any SSH connection from your machine to the Raspberry Pi:
eval "$(ssh-agent -s)" # starting a new shell session ssh-add ~/.ssh/for-raspberry-pi_rsa
Done! This was the last time you had to enter the password for the remote user.
By the way, you can list all the keys that were added to the SSH agent by entering
ssh-add -l
Bluetooth
On most Linux systems, Bluetooth functionality is provided by the kernel module, libraries and utilities of the BlueZ project. So, make sure that BlueZ is installed.
sudo apt install bluez
Bluetooth Low Energy (BLE) Control in Terminal
Set the Bluetooth controller to operate in BLE mode only by editing /etc/bluetooth/main.conf to make sure that the value of ControllerMode is set to le.
ControllerMode = le
If a change had to be made, you need to reboot the system.
Now issue the command
sudo systemctl start bluetooth
to start the bluetooth service. You can also stop, restart and query the status with by replacing start with stop, restart, status, respectively.
With the bluetooth service running, enter the command
bluetoothctl
which runs a Bluetooth control program in the terminal. In this program you can, for example, display the list of commands, query the controller, power on the controller, scan for devices with RSSI 80 or better, advertise with the local name RPi3 and, finally, power off and quit the program with the following commands:
help show power on menu scan rssi 80 back scan on ... scan off discoverable on advertise on menu advertise name RPi3 back ... advertise off power off quit
Bluetooth Programming with D-Bus
Here is an (incomplete!) example of programming D-Bus in C++ using the sdbus-c++ C++ binding, for which you need to install libsdbus-c++-dev.
sudo apt install libbluetooth-dev libsdbus-c++-dev
Inspired by this article from PunchThrough's Andy Lee, I have created a basic BLE peripheral.
#include <sdbus-c++/sdbus-c++.h> #include <iostream> /** * Names of D-Bus system services can be looked up with the terminal command * * busctl list * * We see that the name of the D-Bus system service of the bluetooth daemon is 'org.bluez'. * The D-Bus object path for a given system service can be queried with the terminal command * * busctl tree <service name> * * For 'org.bluez' let us assume the object path is '/org/bluez/hci0'. * We can now introspect the interfaces available in the D-Bus object via * * busctl introspect org.bluez /org/bluez/hci0 * * or * * gdbus introspect -y -d "org.bluez" -o "/org/bluez/hci0" * * We will use to the methods and properties in the published interfaces * to create a BLE peripheral which advertises a service named 'Test'. * Additional information on the methods and their parameters can be looked up from * the BlueZ documentation at https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc */ int main(int numargs, char* args[]) { const char* bluezServiceName = "org.bluez"; const char* bluezObjectPath = "/org/bluez/hci0"; const char* interface_adapter = "org.bluez.Adapter1"; const char* interface_advertisingManager = "org.bluez.LEAdvertisingManager1"; auto bluezProxy = sdbus::createProxy(bluezServiceName, bluezObjectPath); if (bluezProxy == nullptr) { std::cerr << "Error while creating proxy to BlueZ D-Bus service." << std::endl; return EXIT_FAILURE; } bluezProxy->setProperty("Powered").onInterface(interface_adapter).toValue(true); bluezProxy->setProperty("Alias").onInterface(interface_adapter).toValue("Test"); // TO BE DONE bluezProxy->finishRegistration(); std::cout << "BLE Peripheral Example" << std::endl; return EXIT_SUCCESS; }
build with
my_arch=`uname -i` g++ -o ble-peripheral main.cpp -L/usr/lib/${my_arch}-linux-gnu -lsdbus-c++
Video4Linux
Video4Linux is the video pipeline control system in Linux. Start by installing the required packages. On a Debian distribution, type
sudo apt install libv4l-0 v4l-utils v4l-conf libv4lconvert0 v4l2-ctl --list-devices
C++ programming notes
Platform detection
To detect whether code is compiled for the Linux platform you can check if __linux__ is defined.
#ifdef __linux__
(here you can find preprocessor definitions for many operating systems)