Linux System Programming

Elucian MoiseElucian Moise
15 min read

System programming refers to programming at the operating system level. This includes:

  • Writing device drivers to interface with hardware

  • Developing kernel modules and extensions

  • Writing system daemons and services

  • Working with low-level system APIs and libraries like POSIX, glibc, etc.

  • Optimizing applications for performance on Linux

  • Writing system utilities and tools

Some of the languages used for Linux system programming are:

  • C: The main language used for Linux kernel development and system programming in general.

  • C++: Also used for system programming tasks, though not as commonly as C.

  • Assembly: Needed for some very low-level and performance-critical tasks.

Linux provides several useful tools for system programming:

  • make - The build automation tool

  • gcc - The GNU C compiler

  • gdb - The GNU debugger

  • strace - System call tracer

  • ltrace - Library call tracer

There are a few main benefits of learning Linux system programming:

  1. A deeper understanding of how operating systems work - By working at the OS level, you gain a much deeper understanding of how things like processes, memory management, I/O, and scheduling work. This knowledge can be applied to optimizing applications and building better software in general.

  2. Ability to develop low-level software - You can develop software that needs direct access to system resources like hardware devices, kernel APIs, system calls, etc. This includes things like device drivers, system daemons, core system utilities, etc.

  3. Better optimization techniques - By understanding how the OS works and which resources are limited, you can apply optimization techniques that can significantly improve performance and efficiency.

  4. Broader job opportunities - System programming skills are in high demand and can open up more specialized jobs related to OS development, embedded systems, and performance optimization.

  5. Deeper Linux knowledge - Linux system programming exposes you to the internals of Linux and how it differs from other OSes. This can help you become a more well-rounded Linux professional.

The skills are transferable to other Unix-like systems as well like MacOS and FreeBSD. Some of these skills are useful for Windows system programming as well.


Device Drivers

Here is an overview of devices and device drivers in Linux:

Devices: Devices are any hardware component that can be attached to a Linux system. This includes things like hard disks, network cards, mice, keyboards, printers, etc.

Device drivers: Device drivers are software programs that allow the operating system to communicate and interact with hardware devices. They provide an interface between the operating system and the devices.

Linux uses two types of device drivers:

  1. Kernel-mode drivers: These are loaded directly into the Linux kernel. They have full access to the kernel APIs and hardware. Most device drivers are kernel-mode drivers.

  2. User-mode drivers: These run as normal processes in userspace. They communicate with kernel-mode drivers or the kernel through system calls. They are used for less critical devices.

When a device is detected, the corresponding device driver is loaded. The driver then initializes the device and makes it available for use.

Some important points about Linux device drivers:

  • Device drivers are loaded on demand, when the corresponding device is detected.

  • Device drivers are modular in Linux. This means they can be loaded and unloaded without rebooting the system.

  • The /dev directory contains device files that act as an interface to the devices and their drivers.

  • The lsmod command can be used to list the currently loaded kernel modules (drivers).

  • The modprobe command can be used to load and unload kernel modules.


Working with devices

Linux represents devices as files in the /dev directory. These device files act as an interface to access and communicate with the actual devices.

There are different types of device files:

  • Block devices - Represent devices that store data in blocks (hard disks, USB drives, etc.). Accessed using read() and write() system calls.

  • Character devices - Represent devices that stream data (mouse, keyboard, serial ports, etc.). Accessed using read() and write() system calls.

  • Socket devices - Represent network interfaces. Accessed using socket APIs.

  • Virtual devices - Represent virtual or emulated devices like pseudo terminals, null devices, etc.

To work with devices in Linux, you typically:

  1. Identify the device - Check the /dev directory for the corresponding device file. Or use the lsblk, lspci, lsusb commands to list block devices, PCI devices and USB devices respectively.

  2. Access the device file - Open the device file to access the device. For example:

int fd = open("/dev/sda1", O_RDONLY);
  1. Interact with the device - Use read(), write() and ioctl() system calls to read/write data and send device specific commands.

  2. Close the device file - Once done interacting, close the file descriptor to release the device:

close(fd);
  1. Load the driver (if needed) - Use modprobe to load the required device driver.

That gives an overview of devices and working with devices in Linux. Let me know if you have any other questions!


Bash Language

Bash stands for Bourne Again SHell. It is a Unix shell and command language widely used by Linux users. Here are some points on how Bash can be used to handle devices in Linux system programming:

  1. Bash scripts can be used to automatically detect and load device drivers. This can be done using the modprobe command in Bash.

  2. Bash provides command substitution using $() which allows executing commands and inserting their output into the script. This can be used to get device information from commands like lsblk, lspci, lsusb etc.

  3. Bash has if/else, for, while loops and case statements which can be used to write conditional logic based on available devices.

  4. Bash has string manipulation functions like substring extraction, pattern matching etc. This can be useful when parsing device information.

  5. Bash has here documents that allow passing a block of text as input to commands. This can be used to write data to device files.

  6. Bash has command redirection using >, >> which can be used to write output to device files.

  7. Bash has arithmetic expansion $((...)) which allows performing mathematical operations. This can be useful for device calculations.

  8. Bash functions allow breaking down a script into reusable modules. This helps in writing modular device management scripts.

So in summary, Bash is a very useful scripting language for automating device management tasks in Linux system programming. The conditional logic, string manipulation and I/O redirections available in Bash make it ideal for this purpose.


Linux Environment

Linux is a complex operating system made up of several components that work together. The key parts of the Linux environment are:

• The Linux Kernel: This is the core of the operating system. It manages hardware resources and provides interfaces for applications.

• The Shell: This is the command line interface that allows you to interact with the Linux system. Some popular shells are Bash, Zsh, tcsh, etc.

• Processes: Every running program in Linux is a process managed by the kernel.

• Users and Groups: Linux has a user-based security model where users belong to groups and file permissions determine access.

• The Filesystem: Linux organizes all information in a hierarchical filesystem. The kernel provides interfaces to access and manage files and directories.

• Environment Variables: Variables that affect the behavior of processes. They are defined in configuration files.

• System Utilities: Linux comes with many command line utilities to perform system administration tasks.

• System Services (Daemons): Programs that run in the background to provide functionality like networking, scheduling jobs, etc.

• Package Management: Software is distributed in the form of packages that can be installed using package managers.


Global environment:

  • This is the environment that all processes on the system inherit.

  • It contains environment variables that are set system-wide.

  • The global environment is configured in files like /etc/profile, /etc/environment and /etc/bash.bashrc.

  • All users on the system share the global environment.

User environment:

  • Each user has their own environment that is inherited by their processes.

  • It contains variables specific to that user.

  • The user environment is configured in files like ~/.bash_profile, ~/.bashrc and ~/.profile.

  • Only that user can access their environment variables.

Configuration files:

  • /etc/profile - Contains global environment variables and functions. Executed for login shells.

  • /etc/environment - Contains global environment variables.

  • /etc/bash.bashrc - Contains global functions and aliases. Executed for interactive non-login shells.

  • ~/.bash_profile - Contains user-specific environment. Executed for login shells.

  • ~/.bashrc - Contains user-specific functions and aliases. Executed for interactive non-login shells.

  • ~/.profile - Also contains user-specific environment. Executed for login shells.

Important variables:

  • PATH - Contains list of directories to search for commands.

  • HOME - Contains user's home directory path.

  • SHELL - Contains current shell.

  • IFS - Contains Internal Field Separator. Used to separate fields.

  • LANG - Contains system language.

  • USER - Contains current username.

In summary, Linux uses a combination of global and user environments configured through various files to set environment variables that affect system and user processes.


Verify & Modify

Here are the steps to verify and modify system variables in Linux:

Verify system variables:

  • Use the env command to list all the current environment variables. This shows variables from all configuration files.

  • Use the printenv command to list specific variables. For example:

      printenv PATH
      printenv HOME
    
  • Check the configuration files like /etc/profile, /etc/environment, ~/.bashrc, ~/.bash_profile, etc. They contain the definitions of system variables.

Modify system variables:

  • Edit the relevant configuration file and modify the variable definition. For example, to change the PATH variable, edit /etc/profile and modify the PATH line.

  • You can also define new variables in the configuration files. For example:

      export NEW_VARIABLE="value"
    
  • To modify a variable for the current shell only, use the export command:

      export PATH=/new/path:$PATH
    
  • Variables can also be modified temporarily using the env command:

      env NEW_VARIABLE="value" command
    

    This will set NEW_VARIABLE only for the duration of command.

  • After modifying a configuration file, you'll need to either:

    • Source the file (. /etc/profile) to make the changes active in the current shell

    • Or open a new terminal for the changes to take effect.

  • Be careful while modifying system variables as some applications may depend on their default values. Test any changes thoroughly.

So in summary, you verify system variables using env and printenv and modify them by editing configuration files or using the export command. Let me know if you have any other questions!


Best practices

Here are some best practices for system programmers to set up and maintain Linux configuration:

  1. Use a consistent directory structure - Keep related config files in the same directory to make them easy to find. Use sub-directories where needed.

  2. Use version control - Store all config files under version control (like Git) to track changes, revert if needed and collaborate with team members.

  3. Separate config from code - Keep config files separate from application code. This makes them easier to maintain and share across environments.

  4. Use templates - Create templates for config files and populate variables at deployment time. This makes configs reusable and avoids hardcoding values.

  5. Use include directives - Break large config files into smaller ones and include them. This makes individual configs easier to understand and maintain.

  6. Use default values - Provide reasonable default values for all config options. This makes new deployments work out of the box.

  7. Document configs - Add comments to describe what each config option does and what values are recommended. This helps new team members understand the configs.

  8. Automate testing - Write tests to validate that configs are syntactically correct and work as intended. Run the tests with each change.

  9. Avoid hardcoding credentials - Use environment variables or external files to store credentials. This avoids committing sensitive data to version control.

  10. Log changes - Keep a changelog documenting any config changes along with the reason for the change and any potential impact. This helps with troubleshooting.

In summary, the key is to make configs easy to understand, reuse, test, deploy and maintain through good organization, abstraction, documentation, testing and change management practices. Let me know if you have any other questions!


Linux Processes

A Linux process is an instance of a running program that contains executing code, allocated memory and resources. Processes have a lifecycle, can create child processes and communicate with each other. Every running program in Linux is a process. They are managed and scheduled by the Linux kernel.

A process consists of:

  • An executing program (code)

  • Memory (data - variables, stack)

  • Resources allocated (file descriptors, signal handlers)

  • Process ID

  • Parent process ID

  • Status information (state, priority, etc)

  • Processes run in virtual memory and are isolated from each other.

A process goes through various states:

  • Running

  • Ready

  • Waiting

  • Suspended

A process has a lifecycle:

  • Creation

  • Execution

  • Termination

A process can create new processes using the fork system call. Linux uses a hierarchical process model where each process has:

  • A parent process that created it

  • Child processes created by it

Linux processes communicate with each other using:

  • Shared memory

  • Pipes

  • Sockets

  • Signals

Processes are managed and scheduled by the Linux kernel. You can view running processes using the ps command and manage them using kill, nice, renice, top, htop etc. Here are the key things a system programmer needs to know about Linux processes:

Starting processes:

  • Use the exec() function call to replace the current process with a new program.

  • Use the fork() system call to create a child process that executes a program.

  • Use the system() library call to execute a command in a subshell.

  • Use commands like nohup, screen/tmux to start a process that continues running after the terminal is closed.

Monitoring processes:

  • Use the ps command to list running processes. You can filter by user, command name, PID, etc.

  • Monitor specific processes by PID using ps -p <pid>.

  • Use the top or htop commands for a dynamic view of the most resource intensive processes.

  • Monitor CPU, memory, disk I/O and network usage of processes.

  • Use the watch command to run a command repeatedly, e.g. watch -n 1 'ps aux' to update the process list every second.

In the picture below I have taken a screenshot of top processes on my laptop. I have used Gnome Resource Monitor app to show me some properties for all processes and I have clicked %CPU to order them.

Killing processes:

  • Send a SIGTERM signal using kill <pid> to politely ask the process to terminate.

  • Send a SIGKILL signal using kill -9 <pid> to forcefully terminate a hanging process.

  • Use killall <process name> to kill all processes with the given name.

  • Terminate processes by command name using pkill <process name>.

  • Terminate all processes of a user using killall -u <username>.

  • Monitor zombie processes (defunct) using ps -lf and kill them.

So in summary, a system programmer needs to understand how to start, monitor and kill processes to effectively manage a Linux system. This includes using commands like ps, top, kill, etc. and system calls like fork, exec and signal handling.


System Monitoring

These GUI system monitoring applications provide a visual interface to monitor various system parameters like CPU usage, memory usage, disk IO, network traffic, running processes, etc. They allow you to keep an eye on the overall health and performance of your Linux system in an easy and accessible way. In the picture below I have taken a screen-shot from my laptop resources using Gnome System Monitor.

Some of the popular GUI system monitoring applications for Linux are:

• Gnome System Monitor - It is a basic system monitor app that comes pre-installed with the Gnome desktop environment. It allows you to monitor CPU, memory, disk and network usage. It also shows a list of running processes.

• KDE System Monitor - For the KDE desktop environment, KDE System Monitor is a graphical process monitor and system information tool. It provides information about CPU, memory, disk, network and processes.

• Conky - It is a lightweight system monitor meant for displaying system information through widgets on the desktop. It can monitor various system parameters like CPU usage, memory usage, disk IO, network usage, etc.

• GKrellM - It is an older but feature-rich system monitor app. It displays system information and resources usage in customizable real-time graphs and gauges. It can monitor CPU, memory, disk, network, processes and much more.

• htop - Though a command line tool, htop also has a GUI version called gtop. It provides an interactive interface to monitor systemem resourcesnd running processes.

• Glances - Glances has both a text-based and GUI interface. The GUI version provides an overview of the system resources in an easy to read dashboard.

• Gnome System Monitor - It is a basic system monitor app that comes pre-installed with the Gnome desktop environment. It allows you to monitor CPU, memory, disk and network usage. It also shows a list of running processes.


Linux Administrator

Here is an explanation of the role of a Linux system administrator in establishing and maintaining a Linux environment for system programming:

• Install and configure a Linux distribution: The administrator selects and installs a Linux distribution that meets the needs of the developers. This includes installing updates, patches and additional software packages.

• Manage user accounts: The administrator creates user accounts for developers, assigns them to appropriate groups and sets permissions. Developer accounts need access to compiler tools, libraries and other system resources.

• Configure networking: The network needs to be configured to allow developers to access resources like code repositories, documentation, testing environments, etc. The firewall may need customized rules.

• Install development tools: The administrator installs compilers, interpreters, debuggers and other tools required for system programming. This includes the GCC C compiler, Make, Git, an IDE if needed, etc.

• Manage libraries and headers: Many system programming projects depend on system libraries and header files. The administrator ensures these are installed and up to date.

• Configure build environments: The administrator can set up build machines, continuous integration servers and other environments to simplify the build process for developers.

• Manage software packages: The administrator installs, updates and removes software packages as required by development projects. This includes libraries, frameworks and utilities.

• Troubleshoot issues: The administrator helps developers troubleshoot any system-related issues that arise during development. This could include compiler errors, library incompatibilities, permission issues, etc.

• Monitor system health: The administrator monitors system resources like CPU, memory, disk and network usage to ensure there are enough reserves for development work.

• Backup data: The administrator implements backup strategies to ensure developers' data and source code is properly backed up in case of data loss.

In summary, a Linux system administrator plays an important role in setting up the environment, tools and resources required for developers to do effective system programming on Linux.


Disclaim: This article was created with AI assistance. Let me know if you have learned anything new and comment on what else should I add. Of course, I will continue with the Linux study in my next article.

10
Subscribe to my newsletter

Read articles from Elucian Moise directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Elucian Moise
Elucian Moise

Software engineer instructor, software developer and community leader. Computer enthusiast and experienced programmer. Born in Romania, living in US.