1 of 56

Processes and Services

2 of 56

Who am I?

Infrastructure Engineer @ Yelp

Formerly Berkeley / OCF

Chris Kuehl

ckuehl@ocf.berkeley.edu

3 of 56

What is this lecture about?

Chapter 1: Processes

Chapter 2: Init systems & services

4 of 56

CHAPTER ONE

Processes

5 of 56

What is a process?

It’s just ~one running program.

Each process has its own memory space, threads, code, etc.

6 of 56

Process hierarchy

Each process has one parent, typically the process that started it.

Processes can have many children.

7 of 56

Processes vs threads

Both methods of running code “at the same time”

A process is a collection of one or more threads

Threads are all part of the same “program”, share the same memory

Processes communicate over some interface

8 of 56

So why is my laptop running 6000 chrome processes?

9 of 56

How are processes created?

A process “forks” into two separate processes using the fork syscall

The parent keeps its PID, the child gets a new PID.

10 of 56

How many “hello”s get printed?

int main(void) {� fork();� printf("hello: %d\n", getpid());�}

11 of 56

How many “hello”s get printed?

int main(void) {� fork();� printf("hello: %d\n", getpid());�}

int main(void) {� fork();� printf("hello: %d\n", getpid());�}

int main(void) {� fork();� printf("hello: %d\n", getpid());�}

Process ID 1000

Process ID 1000

Process ID 1001

12 of 56

How many “hello”s get printed?

int main(void) {� fork();� printf("hello: %d\n", getpid());�}

int main(void) {� fork();� printf("hello: %d\n", getpid());�}

int main(void) {� fork();� printf("hello: %d\n", getpid());�}

$ ./foo

hello: 27951

hello: 27952

Process ID 1000

Process ID 1000

Process ID 1001

13 of 56

How many “hello”s get printed?

int main(void) {� fork();

fork();

fork();� printf("hello: %d\n", getpid());�}

14 of 56

How many “hello”s get printed?

int main(void) {� fork();

fork();

fork();� printf("hello: %d\n", getpid());�}

$ ./foo

hello: 27956

hello: 27958

hello: 27959

hello: 27957

hello: 27961

hello: 27960

hello: 27963

hello: 27962

15 of 56

How to make the child do something else?

pid_t result = fork();�if (result > 0) {� /* parent process */� sleep(100);

} else if (result == 0) {� /* child process */� sleep(10);

} else { /* error */ }

Process tree after start:

child exits first

16 of 56

What happens when processes exit?

pid_t result = fork();�if (result > 0) {� /* parent process */� sleep(100);

} else if (result == 0) {� /* child process */� sleep(10);

} else { /* error */ }

Process tree after start:

After 10 seconds:

????

17 of 56

When a process exits, it temporarily becomes a zombie:

  • No longer running�
  • Still exists in process table so parent can collect exit status, uses no other resources�
  • Totally normal!

Zombie processes

18 of 56

pid_t result = fork();�if (result > 0) {� /* parent process */� sleep(100);

} else if (result == 0) {� /* child process */� sleep(10);

} else { /* error */ }

(defunct means zombie)

19 of 56

20 of 56

21 of 56

pid_t result = fork();�if (result > 0) {� /* parent process */� sleep(100);

} else if (result == 0) {� /* child process */� exit(123);

} else { /* error */ }

Processes always exit with an “exit code” (int between 0 and 255)

Typically, 0 is success, anything else is an error

22 of 56

Parents need to wait() on children

pid_t result = fork();�if (result > 0) {� /* parent process */int status;� wait(&status);� printf("%d\n", WEXITSTATUS(status));�} else if (result == 0) {� /* child process */� sleep(10);

exit(123);�} else { /* error */ }

(aka reap)

23 of 56

Parents need to wait() on children

pid_t result = fork();�if (result > 0) {� /* parent process */int status;� wait(&status);� printf("%d\n", WEXITSTATUS(status));�} else if (result == 0) {� /* child process */� sleep(10);

exit(123);�} else { /* error */ }

Process tree after start:

After 10 seconds:

Prints out:

(aka reap)

24 of 56

What if the parent exits first?

pid_t result = fork();�if (result > 0) {� /* parent process */

sleep(1);� exit(0);�} else if (result == 0) {� /* child process */� sleep(10);

exit(123);�} else { /* error */ }

Process tree after start:

After 1 second:

Orphaned processes are re-parented under PID 1

parent

child

child

25 of 56

Process signaling

Simple messages to other processes.

  • SIGTERM: “please exit”
  • SIGINT: interrupt, generated by ^C in the terminal
  • SIGKILL: kill immediately without a chance to clean up
  • SIGWINCH: terminal was resized
  • SIGHUP: terminal was closed
  • SIGSTOP / SIGCONT: pause/resume

Typically send using kill or pkill, e.g. kill -9 <pid> or pkill -HUP apache2.

26 of 56

How do processes communicate?

All kinds of inter-process communication (IPC):

  • Exiting with a status code
  • Signals (e.g. SIGTERM, SIGKILL, SIGHUP)
  • Pipes (just like stdin, stdout, stderr)
  • Network (e.g. TCP or UNIX sockets)
  • Message bus (e.g. dbus)
  • ...and tons more...

27 of 56

Working with processes: ps

Nobody remembers what the arguments to ps mean, just memorize a few

ps aux

ps xawuf

ps fux

28 of 56

Working with processes: ps

ps -o pid,uname,comm

29 of 56

Working with processes: pstree

30 of 56

Working with processes: pgrep & pkill

(pgrep is especially useful for composing)

31 of 56

Working with processes: htop

(makes pretty screenshots)

32 of 56

CHAPTER TWO

Init systems & services

33 of 56

What is an init system?

The first process launched (PID 1), lives forever

Core responsibilities:

  • Launching enough processes to make the system useful
  • Reap orphaned zombie processes

34 of 56

Why does it have to reap orphaned zombies?

pid_t result = fork();�if (result > 0) {� /* parent process */

sleep(1);� exit(0);�} else if (result == 0) {� /* child process */� sleep(10);

exit(123);�} else { /* error */ }

Process tree after start:

After 1 second:

Orphaned processes are re-parented under PID 1

parent

child

child

35 of 56

What does a bare-bones init system look like?

/bin/bash can work in a pinch�

What about in code?

36 of 56

Bare-bones init

pid_t result = fork();� if (result > 0) {

/* parent process */while (wait(NULL) != result) {}� } else if (result == 0) {� /* child process */� execl("/bin/cowsay", "cowsay"); // any process here� } else { /* error */ }

(2) reaps all children, even those it didn’t spawn (orphans)

(1) spawns some useful process

37 of 56

Traditional init systems

Even traditional init systems do more than the minimum required.

Typical extra responsibilities:

  • coordinating startup/shutdown (often via “runlevels”)
  • launching services
  • handling signals

38 of 56

Different kinds of processes

Not strict definitions:

  • Interactive: foreground processes like chrome, vim, htop�
  • Daemons: background processes like sshd, httpd, rsyslogd, nginx, postfix

39 of 56

How do we control services?

Lots of ways…

  • We could signal processes (e.g. kill -HUP)
  • We could use their CLIs (rndc, apachectl, etc.)
  • We could rely on our init systems

Let’s look at how init systems do it...

40 of 56

“Old”-style init scripts

Turing-complete, actual scripts:�/etc/init.d/{service} {action}

Each script is responsible for keeping track of processes.

What does that look like?

41 of 56

“Old”-style init scripts

/etc/init.d/nginx on ubuntu

42 of 56

“Old”-style init scripts

Turing-complete, actual scripts:�/etc/init.d/{service} {action}

Each script is responsible for keeping track of processes.

  • Typically PID files (can be stale, race-prone)
  • Inconsistent actions provided (start/stop/restart, reload, graceful, status, etc.)
  • Inconsistent behavior and output (e.g. status command)
  • Incredibly annoying to write, often copy-pasted

43 of 56

What would modern service supervision look like?

Let’s look at some examples...

Traditional

Modern

Scripts that can do anything

Restricted, declarative config files

Store state using PID files

Store state in the init system

Inconsistent actions (each init script its own CLI)

Consistent actions via common CLI

Difficult to control ordering

Ordering via dependencies or events

44 of 56

Modern init system: upstart

Developed by Canonical (Ubuntu), used for a few releases

Event-based (services subscribe to events)

Parallel start and stop

Way easier to write than�traditional init scripts

45 of 56

Modern init system: upstart

description "my cool server"��start on filesystem and started networking�stop on shutdown��exec /usr/bin/my-cool-server

/etc/init/my-cool-server.conf

46 of 56

Modern init system: systemd

Originally comes from Fedora-land

Dependency-based

Parallel start and stop

Way easier to write than traditional init scripts

47 of 56

Modern init system: systemd

Goals:

  • Unify core Linux tooling across distributions
  • Provide excellent UX

Ended up reimplementing syslog, cron, networking, GRUB, ConsoleKit, udev

UNIX philosophy???

48 of 56

Turns out they’re controversial

49 of 56

Turns out they’re controversial

50 of 56

Turns out they’re controversial

51 of 56

In practice, systemd has won

...

52 of 56

systemctl status

systemctl start/stop

systemctl enable/disable

53 of 56

systemctl (no arguments)

54 of 56

journalctl

View recent logs from a unit:�journalctl -u $unit -e

Tail logs from a unit:�journalctl -u $unit -f

55 of 56

Writing systemd unit files

[Unit]

Description=my cool server

Requires=network-online.target opt.mount

After=network-online.target opt.mount

[Service]

ExecStart=/usr/bin/my-cool-service

[Install]

WantedBy=multi-user.target

/etc/systemd/system/my-cool-server.service

56 of 56

Takeaways (aka my opinions)

  • Think about the UNIX philosophy as a guiding principle, not a law�
  • Focus on the health of entire systems�
  • How can you be most productive & design things reliably?