Exploring Linux
Time: 90 min | Prerequisites: Any Linux host (Section 0); SSH Login (Sections 1--6 on the RPi)
The Linux Layered Stack and "Everything Is a File"
An embedded Linux system is built from three layers. At the bottom is the hardware -- the SoC, RAM, and peripherals. Above it sits the kernel, which manages hardware access, scheduling, and memory protection. On top is user space, where your applications, services, and shell sessions run. All communication between user space and hardware passes through the kernel via system calls.
One of the most powerful abstractions in Linux is that everything is a file. Hardware devices appear as entries in /dev/ (e.g., /dev/i2c-1 for the I2C bus). Kernel runtime information is exposed through virtual filesystems: /proc/ provides per-process data and system statistics (CPU info, memory, uptime), while /sys/ exposes hardware attributes (sensor readings, GPIO states, thermal zones). None of these "files" exist on disk -- the kernel generates their contents on the fly when you read them.
This means the same tools you use for regular files (cat, echo, ls) work for inspecting and controlling hardware. Reading a sensor can be as simple as cat /sys/class/thermal/thermal_zone0/temp. This uniform interface is what makes shell scripting so powerful on embedded Linux.
For deeper reading see the Embedded Linux Overview and Linux Fundamentals reference pages.
Learning Objectives
By the end of this lab you will be able to:
- Navigate the Linux filesystem and explain the role of
/dev/,/sys/,/proc/ - Inspect running processes, identify resource usage, and understand scheduling
- Read and interpret file permissions and apply them to device access
- Use pipes, redirects, and shell tools to explore and debug an embedded system
0. Explore Your Host (No RPi Needed)
Info
This section runs on any Linux machine — your lab PC, a laptop with Ubuntu, or WSL on Windows. It covers the same core commands you will later use on the Raspberry Pi. If the RPis are not available yet, start here.
Your Identity and Environment
whoami # Who am I?
id # User ID, group memberships
echo $SHELL # Which shell am I running?
echo $PATH # Where does the shell look for programs?
env | head -10 # First 10 environment variables
System Information
uname -a # Kernel version, architecture, hostname
cat /proc/cpuinfo | head -20 # CPU details
cat /proc/meminfo | head -5 # Memory details
free -h # Human-readable memory summary
df -h # Disk usage
lsmod | head -10 # Loaded kernel modules
Tip
Compare uname -a output on your host vs. the RPi later. The architecture will be different (x86_64 vs aarch64), but the commands are identical — that is the power of a standardized OS.
Files and Directories
ls -la # List all files (including hidden) with details
file ~/.bashrc # What type of file is this?
mkdir ~/lab_practice # Create a directory
cd ~/lab_practice
touch myfile.txt # Create an empty file
ls -la # See ownership, permissions, timestamps
Links — Soft vs Hard
cd ~/lab_practice
echo "hello" > original.txt
ln -s original.txt soft_link # Symbolic (soft) link
ln original.txt hard_link # Hard link
ls -la # Compare: soft link shows →, hard link has same inode
rm original.txt
cat soft_link # Broken — target gone
cat hard_link # Still works — data persists
Redirects and Pipes
echo "Hello world" > ~/lab_practice/greet.txt # Write (overwrite)
echo "Hello again" >> ~/lab_practice/greet.txt # Append
cat ~/lab_practice/greet.txt # Read
grep Hello ~/lab_practice/greet.txt # Search
history | grep touch # Pipe: filter command history
Background Jobs and Job Control
watch -n1 date # Live-updating clock
# Press Ctrl+Z to suspend it
fg # Bring it back to foreground
# Press Ctrl+C to stop it
Network and Time
ip link # Network interfaces (or: ip l)
ip address # Network addresses (or: ip a)
hostname # Machine hostname
date # Current date and time
date "+%s" # Unix epoch (seconds since 1970-01-01)
Shell Navigation Tricks
These keyboard shortcuts work in any Bash session and will save you a lot of typing:
# Ctrl+R → reverse search history (type a fragment, keep pressing Ctrl+R to cycle)
# Ctrl+A / E → jump to beginning / end of line
# Ctrl+W → delete word backward
# Ctrl+L → clear screen (faster than typing 'clear')
# Ctrl+T → swap last two characters (quick typo fix)
# Re-run last command with sudo prepended
sudo !!
# Grab the last argument of the previous command
ls /sys/class/thermal/thermal_zone0/
cd !$ # cd /sys/class/thermal/thermal_zone0/
# Go back to previous directory
cd /etc
cd /var/log
cd - # back to /etc
Tip
Ctrl+R is the single most useful shortcut. Instead of pressing ↑ fifty times to find a command, just press Ctrl+R and type a few letters of what you remember.
Brace Expansion and Quick Copies
# Create multiple directories at once
mkdir -p project/{src,include,build,docs}
# Create a timestamped backup of a file
cp myfile.conf{,.bak.$(date +%F)} # creates myfile.conf.bak.2026-02-19
# Generate sequences
echo {1..5} # 1 2 3 4 5
echo file{A,B,C}.txt # fileA.txt fileB.txt fileC.txt
Checkpoint 0a
| Question | Your Answer |
|---|---|
Your username (whoami) |
|
CPU architecture (uname -m) |
|
Total RAM (free -h) |
|
Root filesystem usage (df -h /) |
|
| Soft link after deleting target — what happens? |
Your First Script
Everything above was interactive — type a command, see output. Real embedded work requires scripts: saved sequences of commands that run unattended.
Create a scripts directory and your first script — copy-paste the entire block:
mkdir -p ~/scripts
cat << 'EOF' > ~/scripts/sysinfo.sh
#!/bin/bash
set -euo pipefail
# Variables and command substitution
HOSTNAME=$(hostname)
KERNEL=$(uname -r)
ARCH=$(uname -m)
TIMESTAMP=$(date +%Y-%m-%dT%H:%M:%S)
MEM_TOTAL=$(grep MemTotal /proc/meminfo | awk '{print $2}')
MEM_FREE=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
PROC_COUNT=$(ps aux | wc -l)
echo "=== System Report ==="
echo "Time: $TIMESTAMP"
echo "Host: $HOSTNAME"
echo "Kernel: $KERNEL ($ARCH)"
echo "Memory: ${MEM_FREE} kB free of ${MEM_TOTAL} kB"
echo "Processes: $PROC_COUNT"
EOF
chmod +x ~/scripts/sysinfo.sh
Tip
cat << 'EOF' > file is a heredoc — it writes everything between << 'EOF' and EOF into the file. The single quotes around 'EOF' prevent the shell from expanding $variables while writing. This is the fastest way to create files from copy-paste — no editor needed. You can also use vim ~/scripts/sysinfo.sh or nano ~/scripts/sysinfo.sh to create and edit scripts interactively.
Run it:
You should see output like this (with your machine's values):
=== System Report ===
Time: 2026-02-19T10:30:15
Host: lab-pc
Kernel: 6.1.0-28-amd64 (x86_64)
Memory: 3215420 kB free of 8108240 kB
Processes: 187
Verify: does the kernel version match uname -r? Does the hostname match hostname?
Tip
#!/bin/bash is the shebang — it tells the kernel which interpreter to use for this file.
Note
What does set -euo pipefail mean?
set is a shell built-in that changes how bash behaves. Each flag turns on a safety net:
| Flag | Meaning | Without it... |
|---|---|---|
-e |
Exit on error — stop the script immediately if any command fails | The script silently continues after errors, potentially doing damage |
-u |
Undefined variables are errors — stop if you use a variable that was never set | $TYPO silently expands to an empty string, causing subtle bugs |
-o pipefail |
Pipe failures are errors — a pipeline fails if any command in it fails, not just the last one | failing_cmd \| grep ok reports success because grep succeeded |
Try it yourself — see the difference:
# Without -e: the script continues after the error
bash -c 'ls /nonexistent; echo "still running"'
# With -e: the script stops at the error
bash -c 'set -e; ls /nonexistent; echo "never reached"'
# Without -u: typo in variable name silently gives empty string
bash -c 'echo "Hello $NAEM"'
# With -u: typo is caught
bash -c 'set -u; echo "Hello $NAEM"'
Always use set -euo pipefail as the first line after the shebang. It is the difference between a script that fails loudly (so you can fix it) and one that fails silently (so you debug for hours).
Edit Your Script
The heredoc created the file — now open it and make a change. Use whichever editor you prefer:
Task: Add a new line that prints the system uptime. Add this line before the final echo "Processes: line:
and a matching echo:
If you are using vim: press /Processes to search for the line, press O (capital O) to open a new line above, type the two lines, press Esc, then :wq to save.
If you are using nano: use Ctrl+W to search for "Processes", type the new lines above it, then Ctrl+O to save and Ctrl+X to exit.
Run the script again to see your change:
You should now see a new Uptime: line in the output. If it is missing, open the file again and check that the new lines are in the right place — before the echo "Processes:" line.
Tip
This is the typical embedded workflow: paste a script to create it quickly, then edit it to adapt. On a minimal system without clipboard support, you may need to type changes by hand — that is where knowing vim pays off.
Conditionals and Loops
Add a second script that checks for files and iterates:
cat << 'EOF' > ~/scripts/check_and_count.sh
#!/bin/bash
set -euo pipefail
# Conditional: does a file exist?
if [[ -f /proc/cpuinfo ]]; then
CORES=$(grep -c "^processor" /proc/cpuinfo)
echo "CPU cores: $CORES"
else
echo "Cannot read CPU info" >&2
fi
# Loop: iterate over thermal zones (if any exist)
FOUND=0
for zone in /sys/class/thermal/thermal_zone*/temp; do
[[ -f "$zone" ]] || continue
TEMP=$(cat "$zone")
NAME=$(basename "$(dirname "$zone")")
echo "$NAME: $(( TEMP / 1000 ))°C"
FOUND=$(( FOUND + 1 ))
done
if [[ $FOUND -eq 0 ]]; then
echo "No thermal zones found (normal on some hosts/VMs)"
fi
# Loop: top 5 memory-consuming processes
echo ""
echo "=== Top 5 Memory Users ==="
ps aux --sort=-%mem | head -6 | awk 'NR>1 {printf " %-6s %5s%% %s\n", $2, $4, $11}'
EOF
chmod +x ~/scripts/check_and_count.sh
Run it:
You should see the CPU core count, any thermal zones with temperatures, and the top 5 memory users. If you see "No thermal zones found" — that is normal on some hosts and VMs; it will work on the RPi.
Arguments and Exit Codes
Scripts become much more useful when they accept arguments from the command line:
cat << 'EOF' > ~/scripts/check_device.sh
#!/bin/bash
set -euo pipefail
# $1 = first argument, $2 = second, $# = number of arguments
if [[ $# -eq 0 ]]; then
echo "Usage: $0 <device_path> [device_path2 ...]" >&2
exit 1 # exit with error code 1
fi
# Loop over all arguments with "$@"
for DEV in "$@"; do
if [[ -e "$DEV" ]]; then
PERMS=$(ls -la "$DEV" | awk '{print $1}')
echo "[OK] $DEV ($PERMS)"
else
echo "[MISS] $DEV"
fi
done
exit 0 # exit with success code 0
EOF
chmod +x ~/scripts/check_device.sh
Try it with different arguments:
~/scripts/check_device.sh # no args → error
~/scripts/check_device.sh /dev/null # one device
~/scripts/check_device.sh /dev/null /dev/zero /dev/i2c-1 # multiple devices
Check the exit code of the last command:
~/scripts/check_device.sh /dev/null
echo $? # 0 = success
~/scripts/check_device.sh
echo $? # 1 = error (no arguments given)
Tip
Exit codes are how scripts communicate success or failure. 0 means success, anything else means error. Every command sets $? — try ls /nonexistent; echo $? to see a failing command's exit code.
While Loops and Reading Input
for loops iterate over a list. while loops repeat until a condition is false — most commonly used to read lines from files or command output:
# Read /proc/mounts line by line, print filesystem types
while read -r _ mountpoint fstype _rest; do
echo "$mountpoint → $fstype"
done < /proc/mounts
# Process command output line by line with a pipe
ps aux | while read -r user pid _rest; do
[[ "$user" == "root" ]] && echo "Root process: PID $pid"
done | head -5
Tip
read -r splits each line into variables by whitespace. The _ and _rest variables are throwaway — a convention for fields you do not need.
Text Processing with grep and awk
These are the two tools you will use most for log analysis and debugging:
# grep: filter lines matching a pattern
dmesg | grep -i "error" # Find errors in kernel log
ps aux | grep bash # Find bash processes
grep -c "^processor" /proc/cpuinfo # Count CPU cores
# awk: extract and format fields
df -h | awk 'NR>1 {print $6, $5}' # Mount point + usage%
ps aux --sort=-%mem | awk 'NR<=6 {print $4, $11}' # Memory% + command
free -m | awk '/^Mem:/ {printf "%.0f%% used\n", $3/$2*100}'
Useful Combos
# Count unique items — sort first, then count
ps aux | awk '{print $1}' | sort | uniq -c | sort -rn # processes per user
# tee: see output AND save to file at the same time
dmesg | grep -i error | tee ~/errors.txt
# column: format messy output into aligned columns
mount | column -t
# Find the 10 largest files in a directory tree
du -ah ~/ 2>/dev/null | sort -rh | head -10
# Simple stopwatch — measures how long any command takes
time sleep 2
Aliases — Teach Your Shell Shortcuts
Tired of typing the same long commands? Create aliases:
# Try these in your terminal right now (temporary — gone after logout)
alias ll='ls -lah'
alias psmem='ps aux --sort=-%mem | head -10'
alias ports='ss -tlnp'
alias dmesg='dmesg --color=always'
To make them permanent, append them to ~/.bashrc:
cat << 'EOF' >> ~/.bashrc
# My aliases
alias ll='ls -lah'
alias la='ls -A'
alias ..='cd ..'
alias ...='cd ../..'
alias psmem='ps aux --sort=-%mem | head -10'
alias pscpu='ps aux --sort=-%cpu | head -10'
alias ports='ss -tlnp'
alias myip='hostname -I'
alias h='history | tail -20'
EOF
source ~/.bashrc
Warning
Note the double >> — this appends to the file. A single > would overwrite your entire .bashrc. You can also open it with vim ~/.bashrc or nano ~/.bashrc if you prefer to edit manually.
Tip
On a minimal embedded system you might not have ~/.bashrc. Use ~/.profile instead — it works on any POSIX shell. You can also use alias inside scripts, but functions (shown below) are usually more flexible.
vim Survival Guide
On your lab PC you have nano, but on embedded systems vim (or its minimal version vi) is often the only editor available. vim is friendlier than plain vi — it has syntax highlighting, better undo, and visual feedback. On most Linux distributions vim is already installed or aliased to vi.
Check which one you have:
vim --version | head -1 # "VIM - Vi IMproved" = you have vim
vi --version 2>&1 | head -1 # on minimal systems this may be BusyBox vi
Open a file (creates it if it does not exist):
vim has two main modes:
| Mode | Purpose | How to enter |
|---|---|---|
| Normal | Navigate, delete, copy, paste | Press Esc (you start here) |
| Insert | Type text — you see -- INSERT -- at the bottom |
Press i, a, or o |
The essential commands:
── Entering Insert Mode ──────────────────
i Insert before cursor
a Insert after cursor
o Open new line below and insert
O Open new line above and insert
Esc Back to Normal mode
── Navigation (Normal mode) ──────────────
h j k l Left, Down, Up, Right (arrow keys also work)
gg Go to first line
G Go to last line
0 Go to beginning of line
$ Go to end of line
w Jump to next word
b Jump to previous word
/pattern Search forward (press n for next, N for previous)
── Editing (Normal mode) ─────────────────
dd Delete (cut) entire line
yy Copy (yank) entire line
p Paste below current line
u Undo (vim supports multiple undo levels, vi only one)
Ctrl+R Redo (vim only)
x Delete character under cursor
dw Delete word
cw Change word (deletes and enters Insert mode)
── Save and Quit ─────────────────────────
:w Save (write)
:q Quit (fails if unsaved changes)
:wq Save and quit
:q! Quit without saving (discard changes)
ZZ Save and quit (shortcut for :wq)
── Useful extras (vim only) ──────────────
:set number Show line numbers
:set nonumber Hide line numbers
:syntax on Enable syntax highlighting
:%s/old/new/g Replace all occurrences of "old" with "new"
Practice — try editing a file:
vim ~/lab_practice/notes.txt
# Press i, type some text, press Esc
# Press dd to delete a line, press u to undo
# Press /text to search, n to jump to next match
# Type :wq to save and quit
Tip
The most common mistake: typing text while in Normal mode. If things go wrong, press Esc first, then u to undo. When in doubt: Esc Esc :q! gets you out without saving.
Note
On BusyBox-based embedded systems (Buildroot) you only get minimal vi — no syntax highlighting, no multi-level undo, no Ctrl+R redo. The core commands (insert, navigate, save, quit) work the same on both.
Putting It Together — A Reusable Function
cat << 'EOF' > ~/scripts/report_lib.sh
#!/bin/bash
# Library: source this from other scripts
system_summary() {
echo "[$(date +%H:%M:%S)] $(hostname) | kernel $(uname -r) | $(ps aux | wc -l) procs | $(free -m | awk '/^Mem:/ {print $7}') MB available"
}
file_exists() {
if [[ -e "$1" ]]; then
echo " [OK] $1"
return 0
else
echo " [MISS] $1"
return 1
fi
}
EOF
Test it:
Tip
source (or .) runs a script in the current shell so its functions become available. This is how you build reusable libraries in bash — the Shell Scripting tutorial uses this pattern for sensor libraries on the RPi.
Your First Python Script
Bash is great for quick automation, but for anything more complex — data processing, networking, hardware interaction — Python is the standard tool on embedded Linux. (Historically, Perl and Tcl filled this role, and you will still find Perl scripts in many Linux system tools — but Python has largely replaced them for new development.) Let's see how it works on the same system.
Check that Python is available:
Create a Python script that reads system info — the same task as sysinfo.sh, but in Python:
cat << 'EOF' > ~/scripts/sysinfo.py
#!/usr/bin/env python3
"""System information script — Python version."""
import os
import sys
from datetime import datetime
from pathlib import Path
def read_file(path):
"""Read a file and return its contents, or None if it doesn't exist."""
try:
return Path(path).read_text().strip()
except (FileNotFoundError, PermissionError):
return None
def get_memory_kb(field):
"""Extract a memory field from /proc/meminfo."""
meminfo = read_file("/proc/meminfo")
if meminfo is None:
return 0
for line in meminfo.splitlines():
if line.startswith(field):
return int(line.split()[1])
return 0
def get_cpu_temp():
"""Read CPU temperature from sysfs (returns °C or None)."""
raw = read_file("/sys/class/thermal/thermal_zone0/temp")
if raw is None:
return None
return int(raw) / 1000
def main():
print("=== System Report (Python) ===")
print(f"Time: {datetime.now():%Y-%m-%dT%H:%M:%S}")
print(f"Host: {os.uname().nodename}")
print(f"Kernel: {os.uname().release} ({os.uname().machine})")
mem_total = get_memory_kb("MemTotal")
mem_free = get_memory_kb("MemAvailable")
print(f"Memory: {mem_free} kB free of {mem_total} kB")
temp = get_cpu_temp()
if temp is not None:
print(f"CPU Temp: {temp:.1f} °C")
# Command-line argument: optional path to check
if len(sys.argv) > 1:
path = sys.argv[1]
exists = "exists" if Path(path).exists() else "NOT found"
print(f"Device: {path} → {exists}")
if __name__ == "__main__":
main()
EOF
chmod +x ~/scripts/sysinfo.py
Run it:
You should see the same system information as the bash version, plus a CPU Temp: line if thermal zones are available. The second run adds a Device: line showing whether the path exists.
Compare the output with the bash version:
Tip
Notice how Python reads /proc/ and /sys/ as regular files — the same virtual filesystems you explored earlier. This works because on Linux everything is a file, and Python's standard open() / Path.read_text() can access kernel interfaces directly. No special library needed.
Note
#!/usr/bin/env python3 is the portable shebang for Python — it finds python3 wherever it is installed, which may differ between your host and the RPi. The bash equivalent is #!/bin/bash.
Checkpoint 0b
| Question | Your Answer |
|---|---|
What does set -euo pipefail do? |
|
Output of your sysinfo.sh — kernel version? |
|
CPU cores found by check_and_count.sh? |
|
What does >&2 mean? |
|
What does $? return after a successful command? |
|
| How do you save and quit in vim? | |
sysinfo.sh vs sysinfo.py — same output? Any differences? |
Connect to the Raspberry Pi
Info
Everything above works identically on the Raspberry Pi. The difference: the RPi also has /dev/i2c*, /dev/spi*, GPIO, and other hardware-specific devices that you cannot explore on your host. Sections 1–6 below run on the RPi.
If you have not set up SSH access yet, complete the SSH Login tutorial first, then come back here.
Find your Pi's IP address — it is on the sticker on the device in the lab. Then connect:
Replace 192.168.x.y with your Pi's actual IP address. The username is linux and the lab password is passwd.
If this is your first connection, SSH will ask you to accept the host key fingerprint — type yes:
The authenticity of host '192.168.28.248 (192.168.28.248)' can't be established.
ED25519 key fingerprint is SHA256:Yvs56UL1A153xUwu/WxbxWBoSNm6+L7p+VdKfPxeI5s.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
After login you should see the Pi's prompt:
Tip
Connection refused or timeout? Check that the Pi is powered (green LED blinks during boot), verify the IP with ping 192.168.x.y, and make sure you are on the same network.
Verify you are on the Pi:
Tip
Try running your scripts on the Pi too — copy them over with scp ~/scripts/*.sh linux@192.168.x.y:~/scripts/ or just re-create them with the heredocs. Compare the output: different architecture, different kernel, same scripts.
1. Processes — Who Is Running?
Every program on Linux is a process with its own memory, permissions, and priority. On a microcontroller you have one program; here you have dozens.
Step 1: List All Processes
Count them:
Tip
On stock Raspberry Pi OS you will see 80-120 processes. On a Buildroot minimal image, this drops to 5-10. The difference is all the services you did not ask for.
Step 2: See the Process Tree
Find PID 1. A PID (Process ID) is a unique number assigned by the kernel to each running process. PID 1 is always the first user-space process — systemd, the init process that starts everything else. Every other process is a descendant of PID 1.
Step 3: Interactive View
Observe: - CPU bars at the top — your Pi has 4 cores - MEM bar — how much RAM is in use - PRI and NI columns — PRI is the kernel's internal scheduling priority, and NI is the nice value (range -20 to 19, default 0). A lower nice value means higher priority — a process with nice -20 gets more CPU time than one with nice 19. Regular users can only increase nice (lower their priority); only root can decrease it.
Note
Press F6 in htop to sort by different columns. Sort by %MEM to find the biggest memory user.
Bonus: Quick Process Tricks
# Which process is using a specific port?
sudo ss -tlnp | grep :22 # who is listening on SSH port?
# Kill a process by name instead of PID
pkill -f "sleep 300" # kills all matching processes
# Run a command and measure its execution time
time ls -lR /sys/class/ > /dev/null
# Watch process count change live
watch -n 2 'ps aux | wc -l'
Checkpoint 1
Fill in:
| Question | Your Answer |
|---|---|
| Total process count | |
| PID 1 process name | |
| Biggest memory user | |
| Number of CPU cores shown |
2. Filesystem — Where Is Everything?
Linux organizes everything in a single directory tree. Three special directories are critical for embedded work — and none of them exist on disk. They are virtual (pseudo) filesystems: the kernel creates them in memory at boot and populates them dynamically. They consume no disk space, and their contents change as hardware and processes come and go.
Step 4: Explore /dev/ (Devices)
Each entry is a device node — a file that represents a piece of hardware. When you cat /dev/i2c-1, you are talking to the I2C bus through the kernel.
Step 5: Explore /sys/ (Kernel Attributes)
The temperature is in millidegrees Celsius. Divide by 1000:
Tip
When you write a kernel driver later in this course, you will create entries in /sys/. This is how user-space applications will read your sensor data.
Step 6: Explore /proc/ (Process and System Info)
The numbered directories in /proc/ are per-process directories. Pick a PID and look inside:
Step 7: Compare the Three
| Filesystem | Mount Point | Content | Created By |
|---|---|---|---|
| devfs | /dev/ |
? | ? |
| sysfs | /sys/ |
? | ? |
| procfs | /proc/ |
? | ? |
Fill in the "Content" and "Created By" columns based on what you observed.
Bonus: Quick Edits on the Pi
On Raspberry Pi OS, both nano and vim are available. Try editing a file on the Pi:
vim /tmp/test_edit.txt # practice here — safe location
# Press i, type "Hello from the Pi", press Esc, type :wq
On a minimal Buildroot image later in the course, nano will not be installed — vi is your only option. The core commands are the same.
Bonus: RPi Hardware Diagnostics
The Raspberry Pi has a built-in diagnostic tool called vcgencmd:
vcgencmd measure_temp # GPU temperature
vcgencmd measure_volts core # core voltage
vcgencmd get_throttled # throttling status (0x0 = OK)
vcgencmd get_mem arm # RAM allocated to CPU
vcgencmd get_mem gpu # RAM allocated to GPU
vcgencmd measure_clock arm # current CPU clock speed
Warning
If get_throttled returns anything other than 0x0, your power supply is insufficient. Common flags: 0x50005 = actively throttled + under-voltage. This causes random crashes and SD card corruption — fix it before continuing.
# I2C tools: scan the bus for connected devices
sudo i2cdetect -y 1 # scan I2C bus 1
sudo i2cget -y 1 0x18 0x05 w # read a register from a device
sudo i2cdump -y 1 0x18 # dump all registers of a device
# See which kernel modules are loaded for your hardware
lsmod | grep -E "i2c|spi|gpio"
# What type of filesystem is on each partition?
lsblk -f
# Live kernel log — like tail -f for hardware events
dmesg -w
Checkpoint 2
| Question | Your Answer |
|---|---|
Number of entries in /dev/ (ls /dev/ \| wc -l) |
|
| CPU temperature (°C) | |
| System uptime (hours) | |
| RAM total / RAM free |
3. Permissions — Who Can Access What?
Step 8: Read Device Permissions
Compare the permissions:
- /dev/i2c-1 — who owns it? What group? Can your user read it?
- /dev/mem — why is this restricted?
- /dev/gpiomem — different from /dev/mem?
Step 9: Permission Denied
Try accessing a restricted device:
You should get "Permission denied." Now try with sudo:
Warning
/dev/mem gives raw access to physical memory. This is why it is restricted to root — a bug could crash the entire system. Kernel drivers exist precisely to avoid this.
Step 10: Understanding Permission Bits
Decode this:
- Owner (root): read + write
- Group (root): read only
- Others: read only
Now check your own home directory:
Checkpoint 3
| Question | Your Answer |
|---|---|
| /dev/i2c-1 owner:group | |
| /dev/i2c-1 permission bits | |
| Can your user read /dev/mem without sudo? | |
Your username (whoami) |
4. Services — What Starts at Boot?
Step 11: List Running Services
Count them:
Step 12: Inspect a Service
Observe: - Active: running (since when?) - Main PID: the process ID - CGroup: the control group (resource container)
Step 13: Find Unnecessary Services
These services are useful on a desktop but unnecessary on an embedded appliance. In the Boot Timing Lab, you will disable them and measure the boot time improvement.
Bonus: Journalctl — The Better Log Viewer
# Follow system log in real time (like tail -f)
journalctl -f
# Show only SSH-related logs
journalctl -u ssh
# Show logs from this boot only
journalctl -b
# Show only errors and worse
journalctl -p err -b
# Show logs from the last 10 minutes
journalctl --since "10 min ago"
Checkpoint 4
| Question | Your Answer |
|---|---|
| Running services count | |
| SSH service PID | |
| Unnecessary services found |
5. Shell Tools — Pipes, Redirects, and Watching
Step 14: Pipes
Chain commands to filter information:
# Find SPI-related kernel messages
dmesg | grep -i spi
# Find I2C devices
dmesg | grep -i i2c
# Top 5 memory users
ps aux --sort=-%mem | head -6
Step 15: Redirects
Save output to a file:
# Save kernel log
dmesg > ~/boot_log.txt
ls -lh ~/boot_log.txt
# Append to a log
echo "Lab started at $(date)" >> ~/lab_notes.txt
echo "Processes: $(ps aux | wc -l)" >> ~/lab_notes.txt
cat ~/lab_notes.txt
Step 16: Watch
Monitor a changing value in real time:
# Watch CPU temperature every 2 seconds
watch -n 2 'echo "scale=1; $(cat /sys/class/thermal/thermal_zone0/temp) / 1000" | bc'
Press Ctrl+C to stop. Now try watching process count:
Step 17: Environment Variables
# See all environment variables
env | head -10
# Check PATH
echo $PATH
# Set a variable for one command
MY_SENSOR=bmi160 echo $MY_SENSOR
Bonus: Network Quick Checks
# See all listening ports and which process owns them
sudo ss -tlnp
# See all active connections
ss -tunap
# Quick DNS lookup
nslookup google.com
# Monitor interrupt counts in real time (useful for I2C/SPI debugging)
watch -n 1 'cat /proc/interrupts | head -20'
# Compare /dev/ entries between host and RPi (run from host)
diff <(ls /dev/) <(ssh pi@raspberrypi ls /dev/) | head -30
Checkpoint 5
| Question | Your Answer |
|---|---|
| SPI entries in dmesg (count) | |
| Size of boot_log.txt | |
| CPU temperature trend (rising/stable/falling) |
6. Scheduling — Who Gets the CPU?
Step 18: Check Process Priorities
- NI: nice value (-20 highest priority → 19 lowest)
- PRI: kernel priority
- CLS: scheduling class (TS = normal, FF = FIFO real-time, RR = round-robin)
Step 19: Experiment with Nice Values
Start a background task with default priority:
Now start one with low priority:
Compare the NI and PRI values.
Step 20: Check Scheduling Policy
You should see SCHED_OTHER (the default fair scheduler). Real-time processes use SCHED_FIFO or SCHED_RR — you will encounter these in the Jitter Measurement lab.
Clean up:
Checkpoint 6
| Question | Your Answer |
|---|---|
| Default nice value | |
| Nice value of your low-priority sleep | |
| Default scheduling policy |
Summary Table
Fill in this comparison based on everything you explored:
| Aspect | Microcontroller (e.g., Pi Pico) | Linux (Raspberry Pi) |
|---|---|---|
| Programs running | ||
| File system | ||
| Hardware access | ||
| Memory protection | ||
| Who manages startup | ||
| User permissions |
What Just Happened?
You explored the Linux system running on your Raspberry Pi — the same system you will build kernel drivers for, write display applications on, and eventually replace with a custom Buildroot image. Every concept from this lab will be used in later tutorials:
/dev/→ you will create entries here with your kernel drivers/sys/→ your drivers will expose sensor data through sysfs- Permissions → your udev rules will set access for device nodes
- Services → you will create a systemd service for your level display
- Scheduling → you will set real-time priority for sensor threads
- Pipes and redirects → you will use these in every debugging session
Challenge
Write a one-page "system report" script (system_report.sh) that outputs:
1. Hostname and kernel version
2. CPU model and core count
3. Total / free RAM
4. Running process count
5. Top 3 memory-consuming processes
6. List of I2C and SPI devices in /dev/
7. CPU temperature
8. Number of running services
Use pipes, redirects, and command substitution. The script should save its output to ~/system_report.txt.
Next Steps
Now that you understand how Linux works from the inside, you're ready to put pixels on screen: Tutorial: Framebuffer Basics