参考引用
- UNIX 环境高级编程 (第3版)
- 黑马程序员-Linux 系统编程
同一个剧本(程序)可以在多个舞台(进程)同时上演。同样,同一个程序也可以加载为不同的进程 (彼此之间互不影响)。如:同时开两个终端,各自都有一个 bash 但彼此 ID 不同
环境变量字符串都是 name = value 这样的形式,大多数 name 由大写字母加下划线组成,一般把 name 的部分叫做环境变量,value 的部分则是环境变量的值。环境变量定义了进程的运行环境,一些比较重要的环境变量的含义如下
PATH
$ echo $PATH
/opt/ros/melodic/bin:/opt/gcc-arm-none-eabi-9-2020-q2-update/bin:/home/yue/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
SHELL
TERM
LANG
HOME
int main(in argc, char* argv[]);
3 个函数用于正常终止一个程序
#include
void exit(int status);
void _Exit(int status);
#include
void _exit(int status);
3 个退出函数都带一个整型参数,称为终止状态 (或退出状态,exit status)。大多数 UNIX 系统 shell 都提供检查进程终止状态的方法
main 函数返回一个整型值与用该值调用exit 是等价的
// 下两行等价
exit(0);
return(0);
对一段程序进行编译,然后运行,并打印终止状态
$ gcc hello.c
$ ./a.out
hello world
$ echo $? # 打印终止状态
0
#include
int atexit(void (*function)(void));
函数返回值
按照 ISO C 的规定,一个进程可以登记多至 32 个函数,这些函数将由 exit 自动调用。称这些函数为终止处理程序,并调用 atexit 函数来登记这些函数
atexit 的参数是一个函数地址,当用此函数时无需向它传递任何参数,也不期望它返回一个值。exit 调用这些函数的顺序与它们登记时候的顺序相反。同一函数如若登记多次,也会被调用多次
一个 C 程序是如何启动和终止的
注意,内核使程序执行的唯一方法是调用一个 exec 函数。进程自愿终止的唯一方法是显式或隐式地 (通过调用 exit) 调用 _exit 或 _Exit。进程也可非自愿地由一个信号使其终止
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[]) {
int i;
for (i = 0; argv[i] != NULL; ++i) {
printf("argv[%d] : %s\n", i, argv[i]);
}
exit(0);
}
$ gcc exec.c -o exec
$ ./exec arg1 TEST foo
argv[0] : ./exec
argv[1] : arg1
argv[2] : TEST
argv[3] : foo
extern char **environ;
$ size /usr/bin/cc /bin/sh
text data bss dec hex filename
1025621 15120 10600 1051341 100acd /usr/bin/cc
110609 4816 11312 126737 1ef11 /bin/sh
$ gcc -static hello.c # 阻止 gcc 使用共享库
$ gcc hello.c # gcc 默认使用共享库
ISO C 说明了 3 个用于存储空间动态分配的函数
#include
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);
函数返回值
函数 free 释放 ptr 指向的存储空间。被释放的空间通常被送入可用存储区池,以后,可在调用上述 3 个分配函数时再分配
可能产生的致命性的错误
如若一个进程调用 malloc 函数,但却忘记调用 free 函数,那么该进程占用的存储空间就会连续增加,这被称为泄漏 (leakage)。如果不调用 free 函数释放不再使用的空间,那么进程地址空间长度就会慢慢增加,直至不再有空闲空间。此时,由于过度的换页开销,会造成性能下降
#include
char* getenv(const char* name);
#include
int putenv(char *string);
#include
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);
函数返回值
setenv 将 name 设置为 value。如果在环境中 name 已经存在,那么
unsetenv 删除 name 的定义。即使不存在这种定义也不算出错
每个进程都有一个非负整型表示的唯一进程 ID
虽然进程 ID 是唯一的,但是进程 ID 是可复用的
系统中有一些专用进程,但具体细节随实现而不同
除了进程 ID,每个进程还有一些其他标识符,下列函数返回这些标识符
#include
pid_t getpid(void); // 返回值:调用进程的进程 ID
pid_t getppid(void); // 返回值:调用进程的父进程 ID
uid_t getuid(void); // 返回值:调用进程的实际用户 ID
uid_t geteuid(void); // 返回值:调用进程的有效用户 ID
gid_t getgid(void); // 返回值:调用进程的实际组 ID
gid_t getegid(void); // 返回值:调用进程的有效组 ID
#include
pid_t fork(void);
在 fork 之后处理文件描述符有以下两种常见的情况
父进程和子进程之间的对比
使 fork 失败的两个主要原因
fork 有以下两种用法
#include
#include
#include
#include
#include
#include
int globvar = 6;
char buf[] = "a write to stdout\n";
int main(int argc, char* argv[]) {
int var;
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1) {
perror("write error");
exit(1);
}
printf("before fork\n");
if ((pid = fork()) < 0) {
perror("fork error");
exit(1);
} else if (pid == 0) {
globvar++;
var++;
} else {
sleep(2); // 父进程使自己休眠 2s,以此使子进程先执行
}
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);
return 0;
}
$ gcc fork.c -o fork
$ ./fork
a write to stdout
before fork
pid = 2244, glob = 7, var = 89 # 子进程的变量值改变了
pid = 2243, glob = 6, var = 88 # 父进程的变量值没改变
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[]) {
printf("before fork-1-\n");
printf("before fork-2-\n");
printf("before fork-3-\n");
printf("before fork-4-\n");
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
} else if (pid == 0) {
printf("---child is created, pid = %d, parent-pid : %d\n", getpid(), getppid());
} else if (pid > 0) {
sleep(1); // 给父进程增加一个等待命令,这样能保证子进程完成时,父进程处于执行状态,子进程就不会成孤儿
printf("---parent process : my child is %d, my pid : %d, my parent pid : %d\n", pid, getpid(), getppid());
}
printf("------end of file\n");
return 0;
}
$ gcc fork2.c -o fork2
$ ./fork2
before fork-1-
before fork-2-
before fork-3-
before fork-4-
---child is created, pid = 2475, parent-pid : 2474
------end of file
---parent process : my child is 2475, my pid : 2474, my parent pid : 1887
------end of file
# 写的所有进程都是 bash 的子进程
$ ps aux | grep 1887
yue 1887 0.0 0.0 25124 6048 pts/0 Ss 08:41 0:00 bash
yue 2477 0.0 0.0 16180 1088 pts/0 S+ 09:39 0:00 grep --color=auto 1887
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[]) {
int i;
pid_t pid;
for (i = 0; i < 5; i++) {
if (fork() == 0) {
break;
}
}
if (5 == i) {
sleep(5);
printf("I'm parent \n ");
} else {
sleep(i);
printf("I'm %dth child\n", i + 1);
}
return 0;
}
$ gcc mulfork.c -o mulfork
$ ./mulfork
I'm 1th child
I'm 2th child
I'm 3th child
I'm 4th child
I'm 5th child
I'm parent
#include
#include
#include
int var = 100; //.data
int main(void) {
pid_t pid;
pid = fork();
if(pid == -1){ // son
perror("fork error");
exit(1);
} else if (pid > 0) {
var = 288;
printf("parent, var = %d\n", var);
printf("I'm parent pid = %d, getppid = %d\n", getpid(), getppid());
} else if (pid == 0) {
var = 200;
printf("I'm child pid = %d, ppid = %d\n", getpid(), getppid());
printf("child, var = %d\n", var);
}
printf("------finish------\n");
return 0;
}
$ gcc shared.c -o shared
$ ./shared
parent, var = 288
I'm parent pid = 2702, getppid = 1887
------finish------
I'm child pid = 2703, ppid = 2702
child, var = 200
------finish------
注意,一定要在 fork 函数调用之前设置才有效
$ gcc mulfork.c -o mulfork -g
$ gdb mulfork
(gdb) list
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8
9 int main(int argc, char* argv[]) {
10 int i;
(gdb) l
11 pid_t pid;
12
13 for (i = 0; i < 5; i++) {
14 if (fork() == 0) {
15 break;
16 }
17 }
18
19 if (5 == i) {
20 sleep(5);
(gdb) b 13
Breakpoint 1 at 0x6e9: file mulfork.c, line 13.
(gdb) r
Starting program: /home/yue/test/mulfork
Breakpoint 1, main (argc=1, argv=0x7fffffffdbe8) at mulfork.c:13
13 for (i = 0; i < 5; i++) {
(gdb) n
14 if (fork() == 0) {
(gdb) set follow-fork-mode child
(gdb) n
[New process 2831]
[Switching to process 2831]
main (argc=1, argv=0x7fffffffdbe8) at mulfork.c:15
15 break;
(gdb) I'm 2th child
I'm 3th child
I'm 4th child
I'm 5th child
I'm parent
n
19 if (5 == i) {
(gdb) n
23 sleep(i);
(gdb) n
24 printf("I'm %dth child\n", i + 1);
(gdb) n
I'm 1th child
27 return 0;
(gdb)
有 8 种方式使进程终止 (termination)
不管进程如何终止,最后都会执行内核中的同一段代码
对上述任意一种终止情形,都希望终止进程能够通知其父进程它是如何终止的
孤儿进程
僵尸进程 (zombie)
一个由 init 程收养的进程终止时会不会变成个僵尸进程?
#include
#include
#include
int main(int argc, char* argv[]) {
pid_t pid;
pid = fork();
if (pid == 0) {
while (1) {
printf("I am child, my parent pid = %d\n", getppid());
sleep(1);
}
} else if (pid > 0) {
printf("I am parent, my pid is = %d\n", getpid());
sleep(9);
printf("------parent going to die------\n");
} else {
perror("fork");
return 1;
}
return 0;
}
$ gcc orphan.c -o orphan
$ ./orphan
I am parent, my pid is = 4464
I am child, my parent pid = 4464
I am child, my parent pid = 4464
I am child, my parent pid = 4464
I am child, my parent pid = 4464
I am child, my parent pid = 4464
I am child, my parent pid = 4464
I am child, my parent pid = 4464
I am child, my parent pid = 4464
I am child, my parent pid = 4464
------parent going to die------
I am child, my parent pid = 1112
I am child, my parent pid = 1112
I am child, my parent pid = 1112
...
# 父进程死亡前
$ ps ajx
4231 4383 4383 4231 pts/0 4383 S+ 1000 0:00 ./orphan
4383 4384 4383 4231 pts/0 4383 S+ 1000 0:00 ./orphan
# 父进程死亡后
$ ps ajx
1112 4384 4383 4231 pts/0 4231 S 1000 0:00 ./orphan
#include
#include
#include
int main(int argc, char* argv[]) {
pid_t pid;
pid = fork();
if (pid == 0) {
printf("------child, my parent = %d, going to sleep 10s\n", getppid());
sleep(10);
printf("------child die------\n");
} else if (pid > 0) {
while (1) {
printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
sleep(1);
}
} else {
perror("fork");
return 1;
}
return 0;
}
$ gcc zoom.c -o zoom
$ ./zoom
I am parent, pid = 4660, myson = 4661
------child, my parent = 4660, going to sleep 10s
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
------child die------
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
I am parent, pid = 4660, myson = 4661
...
# 子进程死亡前
$ ps ajx
4505 4660 4660 4505 pts/3 4660 S+ 1000 0:00 ./zoom
4660 4661 4660 4505 pts/3 4660 S+ 1000 0:00 ./zoom
# 子进程死亡后
$ ps ajx
4505 4660 4660 4505 pts/3 4660 S+ 1000 0:00 ./zoom
4660 4661 4660 4505 pts/3 4660 Z+ 1000 0:00 [zoom] # defunct 代表死亡
# 每个进程结束后都必然会经历僵尸态,时间长短的差别而已
# 回收僵尸进程,得 kill 它的父进程,让孤儿院去回收它
$ kill -9 4660
一次 wait/waitpid 函数调用,只能回收一个进程
#include
pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);
函数返回值
这两个函数的区别
这两个函数的参数 status 是一个整型指针
检查 wait 和 waitpid 所返回的终止状态的宏
如果要等待一个指定的进程终止 (假设知道要等待进程的 ID) 那么该如何做呢?
waitpid 函数返回终止子进程的进程 ID,并将该子程的终止状态存放在由 ststus 指向的存储单元中。对于 wait,其唯一的出错是调用进程没有子进程(函数调用被一个信号中断时也可能返回另一种出错),但是对于 waitpid,如果指定的进程或进程组不存在,或者参数 pid 指定的进程不是调用进程的子进程,都可能出错
waitpid 的 options 常量
#include
#include
#include
#include
int main(int argc, char* argv[]) {
pid_t pid, wpid;
int status;
pid = fork();
// 返回的值为 0,表示当前进程是子进程
if (pid == 0) {
printf("---child, my id = %d, going to sleep 5s\n", getpid());
sleep(10);
printf("------child die------\n");
// 子进程执行完毕后,将返回 66,表示子进程正常终止
return 66;
} else if (pid > 0) {
// wpid = wait(NULL); // 不关心子进程结束原因
wpid = wait(&status); // wait() 函数会使当前进程阻塞,直到一个子进程终止
if (wpid == -1) {
perror("wait error");
exit(1);
}
if (WIFEXITED(status)) { // 判断子进程是否正常终止
printf("child exit with %d\n", WEXITSTATUS(status));
}
if (WIFSIGNALED(status)) { // 判断子进程是否被信号终止
printf("child kill with signal %d\n", WTERMSIG(status));
}
printf("------parent wait finish: %d\n", wpid);
} else {
perror("fork");
return 1;
}
return 0;
}
$ gcc zoom_test.c -o zoom_test
$ ./zoom_test
---child, my id = 2774, going to sleep 10s
------child die------
child exit with 66
------parent wait finish: 2774
# 测试子进程被信号终止
$ ./zoom_test
---child, my id = 2864, going to sleep 5s
child kill with signal 9
------parent wait finish: 2864
# 另开一个终端,输入下列指令
$ kill -9 2864
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[]) {
int i;
pid_t pid, wpid, tmpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) { // 循环期间, 子进程不 fork
break;
}
if (i == 2) {
tmpid = pid;
printf("--------pid = %d\n", tmpid);
}
}
if (5 == i) { // 父进程, 从表达式 2 跳出
// sleep(5);
//wait(NULL); // 一次wait/waitpid函数调用,只能回收一个子进程.
//wpid = waitpid(-1, NULL, WNOHANG); // 回收任意子进程,没有结束的子进程,父进程直接返回0
//wpid = waitpid(tmpid, NULL, 0); // 指定一个进程回收, 阻塞等待
printf("i am parent , before waitpid, pid = %d\n", tmpid);
//wpid = waitpid(tmpid, NULL, WNOHANG); // 指定一个进程回收, 不阻塞
wpid = waitpid(tmpid, NULL, 0); // 指定一个进程回收, 阻塞回收
if (wpid == -1) {
perror("waitpid error");
exit(1);
}
printf("I'm parent, wait a child finish : %d \n", wpid);
} else { // 子进程, 从 break 跳出
sleep(i);
printf("I'm %dth child, pid= %d\n", i+1, getpid());
}
return 0;
}
$ gcc waitpid_test.c -o waitpid_test
$ ./waitpid_test
--------pid = 3133
i am parent , before waitpid, pid = 3133
I'm 1th child, pid= 3131
I'm 2th child, pid= 3132
I'm 3th child, pid= 3133
I'm parent, wait a child finish : 3133
$ I'm 4th child, pid= 3134
I'm 5th child, pid= 3135
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[]) {
int i;
pid_t pid, wpid, tmpid;
for (i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) {
break;
}
}
if (5 == i) {
while ((wpid = waitpid(-1, NULL, WNOHANG)) != -1) {
if (wpid > 0) {
printf("wait child %d\n", wpid);
} else if (wpid == 0) {
sleep(1);
continue;
}
}
} else {
sleep(i);
printf("I'm %dth child, pid = %d\n", i+1, getpid());
}
return 0;
}
$ gcc waitpid_while.c -o waitpid_while
$ ./waitpid_while
I'm 1th child, pid = 3360
wait child 3360
I'm 2th child, pid = 3361
wait child 3361
I'm 3th child, pid = 3362
I'm 4th child, pid = 3363
wait child 3362
wait child 3363
I'm 5th child, pid = 3364
wait child 3364
#include
extern char **environ;
// 字母 p(path) 表示该函数取 filename 作为参数,并且用 PATH 环境变量寻找可执行文件
// 字母 l(list) 表该函数取一个参数表,它与字母 v 互斤
// 字母 v(vector) 表示该函数取一个 argv[] 矢量
// 字母 e(environment) 表示该函数取 envp[] 数组,而不使用当前环境
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
函数返回值
在很多 UNIX 实现中,这 7 个函数中只有 execve 是内核的系统调用。另外 6 个只是库函数,它们最终都要调用该系统调用。这 7 个函数之间的关系如下图
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[]) {
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(1);
} else if (pid == 0) {
// execlp("ls", "-l", "-h", NULL); // 错误,可变参数是从 argv[0] 开始计算
// execlp("ls", "ls", "-l", "-h", NULL);
// execlp("date", "date", NULL);
// execl("/bin/ls", "ls", "-l", "-h", NULL);
// NULL 为必须提供的,称为 “哨兵”
char* argv[] = {"ls", "-l", "-h", NULL};
execvp("ls", argv);
perror("exec error");
exit(1);
} else if (pid > 0) {
sleep(1); // 让父进程延时 1 秒,保证终端提示符不和输出干扰
printf("I'm parent : %d\n", getpid());
}
return 0;
}
$ gcc fork_exec.c -o fork_exec
$ ./fork_exec
total 152K
-rwxrwxr-x 1 yue yue 8.6K 9月 16 15:42 a.out
-rwxrwxr-x 1 yue yue 8.2K 9月 16 16:15 exec
-rw-rw-r-- 1 yue yue 282 9月 16 16:15 exec.c
-rwxrwxr-x 1 yue yue 8.2K 9月 15 16:55 fcntl
-rwxrwxr-x 1 yue yue 8.3K 9月 15 18:46 fcntl2
-rwxrwxr-x 1 yue yue 8.5K 9月 17 08:52 fork
-rwxrwxr-x 1 yue yue 8.5K 9月 17 09:38 fork2
-rw-rw-r-- 1 yue yue 672 9月 17 09:37 fork2.c
-rw-rw-r-- 1 yue yue 627 9月 17 08:52 fork.c
-rwxrwxr-x 1 yue yue 8.4K 9月 17 16:33 fork_exec
-rw-rw-r-- 1 yue yue 447 9月 17 16:33 fork_exec.c
-rwxrwxr-x 1 yue yue 8.6K 9月 15 19:33 ls-R
-rw-rw-r-- 1 yue yue 943 9月 15 19:31 ls-R.c
-rwxrwxr-x 1 yue yue 12K 9月 17 14:18 mulfork
-rw-rw-r-- 1 yue yue 398 9月 17 11:30 mulfork.c
-rw-r--r-- 1 yue yue 262 9月 15 18:46 mycat.c
-rwxrwxr-x 1 yue yue 8.4K 9月 17 11:32 shared
-rw-rw-r-- 1 yue yue 572 9月 17 11:32 shared.c
I'm parent : 3964
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[]) {
int fd;
fd = open("ps.out", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open error");
exit(1);
}
dup2(fd, STDOUT_FILENO);
execlp("ps", "ps", "aux", NULL);
close(fd);
return 0;
}
$ gcc exec_ps.c -o exec_ps
$ ./exec_ps
$ cat ps_out