Understanding Linux Environment Variables and Process Context
Process Execution and Context
In a typical single-core CPU system, multiple processes do not run simultaneously but are executed concurrrently through rapid time-slicing. Each process is allocated a time slice; if it doesn't finish within that interval, the operating system saves its state and switches to another process.
The CPU uses several registers to manage execution:
- Program Counter (PC/EIP): Holds the address of the next instruction to execute.
- General-purpose registers (EAX, EBX, ECX, EDX): Store temporary data for fast access.
- Stack pointers (ESP, EBP): Manage function call stacks.
- Status register: Tracks CPU state flags.
Collectively, the contents of these registers represent the process context. During a context switch, the OS saves this context before loading another process’s state, enabling seamless multitasking.
Environment Variables Overview
Environment variables are key-value pairs (e.g., PATH=/usr/bin:/bin) that configure process behavior globally. They are inherited by child processes from their parent (typically the shell).
Common Enviroment Variables
- PATH: Colon-separated list of directories where executable programs are located. Running
./myprogramexplicitly specifies a path outsidePATH. - HOME: User's home directory; set automatically upon login.
- SHELL: Path to the current shell interpreter (e.g.,
/bin/bash). - USER / LOGNAME: Current username.
- PWD / OLDPWD: Current and previous working directories (
cd -usesOLDPWD). - HISTSIZE: Number of commands stored in history.
- LS_COLORS: Defines color schemes for
lsoutput. - SSH_CLIENT / SSH_TTY: Metadata about SSH connections.
Use env to list all environment variables or echo $VAR to inspect a specific one.
Accessing Enviroment Variables in Code
C programs can retrieve environment variables using getenv():
#include <stdlib.h>
#include <stdio.h>
int main() {
char* user = getenv("USER");
if (user) printf("User: %s\n", user);
return 0;
}
Alternatively, the third argument to main() provides direct access:
int main(int argc, char *argv[], char *envp[]) {
for (int i = 0; envp[i] != NULL; i++) {
printf("%s\n", envp[i]);
}
return 0;
}
This envp array contains all environment strings, identical to the output of the env command.
Local vs. Environment Variables
Variables created in a shell without export are local and not inherited by child processes:
$ MY_VAR=hello # local variable
$ ./my_program # cannot access MY_VAR
$ export MY_VAR # now it's an environment variable
$ ./my_program # can access MY_VAR
Use set to view all shell variables (including local ones), and unset to remove them.
Built-in vs. External Commands
Commands like cd and echo are built-ins—executed directly by the shell without forking a child process. Thus, they can access local variables, unlike external commands (e.g., ls, grep), which run in separate processes and only inherit exported environment variables.
This distinction explains why echo $MY_VAR works even for local variables: the shell expands the variable before invoking the built-in echo.