System Programming

" One vision, one purpose. "

Copyright © Tony's Studio 2020 - 2022


Chapter Six - Process

6.1 Meet Process

6.1.1 Program and Process

Program is a passive entity, and is stored in hard disk. Process is an active entity, and got a Program Counter and a whole set of resources and runs. When an executable is loaded into memory, it becomes a process.

6.1.2 Process State

  • new: under creation
  • running: literally
  • waiting: waiting for some events, e.g. I/O or signal
  • ready: waiting for processer arrangement
  • terminated: process finished
image-20221225112439583

6.2 Start and Exit

“Action speaks louder than words.”

6.2.1 Start Process

6.2.1.1 fork

fork() is a common way to create an identical process.

1
2
#include <unistd.h>
pid_t fork();

Parent process return child process’s pid and child process return 0 if succeeded. Otherwise -1 is returned and we can use perror() to show error info.

6.2.1.2 exec

There are a lot of these stuffs.

1
2
3
4
5
6
7
8
#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, .../* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);

All arguments should end with NULL. Only execve is a system call.

  • l“ for list, so argv is an argument list.
  • v“ for vector, so argv is an array.
  • e“ for environment, to contain envp array. If no such thing, default environment variables will be passed rather than no environment.
  • p“ for environment PATH, it indicates that system should go to PATH to find filename.

Notice that, unlike we type in commands in terminal, executable name won’t be added by default to the argument list, and need to be added manually. So, the two ways below have different output. It depends on how the target program parse its arguments. If use arg[i] directly, it may cause some error, while getopt() may hide some errors.

1
2
execvp("ls", "-l", NULL);        // equal to $ ls
execvp("ls", "ls", "-l", NULL); // equal to $ ls -l

6.2.2 Exit Process

6.2.2.1 Exit Process

We can wait until process until return in main, or use exit functions to exit somewhere else.

1
2
3
4
5
6
#include <stdlib.h>
void exit(int status); // Do some cleaning, and then go to kernal.
void _Exit(int status); // Go to kernal directly.

#include <unistd.h>
void _exit(int status); // GO to kernal directly.

When exit() is called, program will flush buffer stream, call functions that registered by atexit() and on_ext and other functions related to exit(), then call _exit() at last.

6.2.2.2 Exit Status

We can get a process’ exit status by waiting it.

1
2
3
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

wait() will jam current process until any of the child process returns, and itself return the pid of the returned child process.

waitpid() can wait for a specific process, and is more complicated.

pid meaning
> 0 wait for specific child process with given pid
= 0 wait for any child process whose group id is the same as current process
= -1 wait for any child process, same as wait()
< -1 wait for any child process whose group id is the same as |pid|
options meaning
WNOHANG no block, if no child process returns, return immediately
WUNTRACED return immediately if child process goes into waiting status
0 same as wait()

However, there is one more thing. When we want to wait for a process and get its return status, we need something more. WIFEXITED indicates whether process is exited normally or not, and returns a boolean value, not 0 means true. Then, we use WEXITSTATUS to get the exit status.

1
2
3
4
5
if (waitpid(-1, &status, 0) > 0)
{
if (WIFEXITED(status) != 0)
printf("Exit status: %d\n", WEXITSTATUS(status));
}

Similarly, WIFSIGNALED indicates whether process is stopped by a signal, with WTERMSIG to tell which signal it was.

6.3 Special Process

6.3.1 Some Concepts

  • Process Group: 进程组是一组进程的集合,由进程组PID表示。 每个进程必须有一个进程组PID,即必须属于某个进程组,一个终端的控制进程是它所发起的一系列进程的进程组。
  • Session: 会话期是由一个或者多个进程组组成的集合,开始于用户登录,结束于用户退出,在此期间,用户所运行的所有进程属于该会话期。

6.3.2 Daemon Process

Code is more convincing, huh?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*
Create a daemon process.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(void)
{
// Step 1: Create a child process and terminate the parent.
pid_t pid = fork();
if (pid < 0)
{
fprintf(stderr, "Fork error!\n");
return -1;
}
else if (pid > 0)
return 0;
// Child process now becomes an orphan.

// Step 2: Detach the child process by creating a new session.
setsid();
// Step 3: Change working directory, or current directory can't be deleted.
chdir("./");
// Step 4: Change file access mask.
umask(0);
// Step 5: Close file descriptions, no interactions.
for (int i = 0; i < 3; i++)
close(i);

// Now, the real work of daemon starts, print log info to a temp file.
int fd;
int cnt = 0;
char buffer[32];
for (; ; )
{
if ((fd = open("/tmp/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0600)) < 0)
{
perror("open");
exit(1);
}
sprintf(buffer, "Execute Order %d\n", cnt);
write(fd, buffer, strlen(buffer));
close(fd);
if (cnt == 66)
break;
cnt++;
sleep(10);
}

return 0;
}

For the header files… hard to remember.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
void perror ( const char * str );

#include <unistd.h>
int setsid(void);
int chdir(const char *path);
pid_t fork();

#include <sys/stat.h>
mode_t umask(mode_t mask);

#include <fcntl.h>
int open(const char *pathname, int flags, ...);
int close(int fd);
size_t write(int fd, void *buffer, size_t nbytes);

6.3.3 Zombie Process

If child process dies before parent, and parent doesn’t wait for it, which means it is not cleaned in time, then the child process will become a zombie process, and marked with [defunct] is process list.

One zombie is not that serious, but too many zombies may use up system pid, which prevent other processes to be created.

One more thing, zombie can not be killed by kill -9, it can only be released when parent wait for it, or parent exited, or system rebooted.

6.3.4 Orphan Process

If parent process exited before child process, then the child will become an orphan. We don’t want orphan, so it will then be taken care of by init process.


" Do or do not. There is no try. "

Copyright © Tony's Studio 2020 - 2022

- EOF -