进程的基础知识
1. 程序和进程
2. 查看进程信息
3. 进程的状态
进程的创建
1. fork 创建一个子进程
- 通俗的讲,这个函数调用一次但可能"返回两次"。
- 父进程中调用fork,在创建子进程成功的情况下,会在父进程中返回子进程pid,并会在子进程中返回0。
- 如果没有创建成功,只会在父进程中返回-1。
2. 代码示例
t_stdio.h
#ifndef T_STDIO_H_
#define T_STDIO_H_
#include <stdio.h>
#define E_MSG(STRING, VAL) do{perror(STRING); return(VAL);}while(0)
#endif
fork.c
#include "t_stdio.h"
#include <unistd.h>
int main(void){
pid_t pid = fork();
if(pid == -1)E_MSG("fork", -1);
if(pid == 0){
// 在子进程中执行的代码
printf("child process ...\n");
} else {
// 在父进程中执行的代码
printf("parent process ...\n");
}
// 父子进程都会执行的代码
printf("parent and child process ...\n");
return 0;
}
# 有可能先打印 parent process..., 也有可能先打印 child process ...
# 取决与系统的调度, 并且父进程是不会等子进程的,除非用到wait等系统调用
$ ./a.out
parent process ...
parent and child process ...
child process ...
parent and child process ...
3. 父子进程共享父进程PCB
- 子进程和父进程共享父进程的PCB,节省了资源开销
- 上图是父子进程的代码段,一模一样,因为子进程和父进程用的映像(代码段,数据段, 栈段, 堆等)是同一个(只有写时才复制相应的部分)。只不过根据fork返回值的不同,父子进程执行了不同代码而已。
- 左图是父进程的代码段,当父进程中fork函数执行完,如果返回值是-1时,那么子进程不会存在了(右图不会出现)。
- 如果父进程fork函数成功了,返回值就是子进程的pid。右图出现,子进程和父进程共用一个映像。 父进程继续会执行 printf(“parent process …\n”); 然后继续执行 printf(“parent and child process …\n”);
- 子进程中的fork函数返回值是0, 子进程会执行 printf(“child process …\n”); 和 printf(“parent and child process …\n”);
进程的终止
1. return 和 exit(3) 的区别
- 0377 是8进制,转成2进制就是11 111 111,exit 会将参数的值转为2进制,然后和 11 111 111做与运算(只要低八位),然后转为10进制返回给父进程
- ./a.out 创建的进程的父进程就是当前这个 bash
- echo $? 显示当前命令的返回值
2. 代码示例
exit.c
#include <stdlib.h>
#include <stdio.h>
int main(void){
// getchar();
exit(3);
return 0;
}
// $ gcc exit.c
// $ ./a.out
// $ echo $?
// 3
#include <stdlib.h>
#include <stdio.h>
int main(void){
// -1的二进制表示方式(低8位)是11 111 11
exit(-1); //return -1; 结果好像也是255
return 0;
}
// $ gcc exit.c
// $ ./a.out
// $ echo $?
// 255
遗言函数
1. 什么是遗言函数
2. 向进程注册遗言函数 atexit(3)
- atexit(3)的参数变量是一个函数指针类型的
- 子进程继承父进程的遗言函数,因为父子进程共享同一个映像,所以父进程能执行的遗言函数,子进程终止时也能执行。
atexit.c
#include <stdlib.h>
#include "t_stdio.h"
#include <unistd.h>
// 进程的遗言函数
void bye(void){
printf("bye...\n");
}
void goodbye(void){
printf("goodbye...\n");
}
int main(void){
// 向进程注册遗言函数
atexit(bye);
atexit(goodbye);
// 验证子进程继承父进程的遗言函数
// 创建子进程需要在注册之后,不然无法继承父进程的遗言函数
pid_t pid=fork();
if(pid == -1)E_MSG("fork", -1);
return 0;
}
# 观察输出发现,遗言函数的注册顺序和调用顺序相反
$ ./a.out
goodbye...
bye...
goodbye...
bye...
3. 向进程注册遗言函数 on_exit(3)
#include <stdlib.h>
#include "t_stdio.h"
#include <unistd.h>
// 遗言函数的定义
// return 或 exit后面的参数(没有与0377做与运算)会传递给n
void bye(int n, void *arg){
printf("bye...%d...%s\n", n, (char *)arg);
}
int main(void){
// 向进程注册遗言函数, 一样,调用顺序和注册顺序相反
on_exit(bye, "hello");
// 验证子进程继承父进程的遗言函数
// 创建子进程需要在注册之后,不然无法继承父进程的遗言函数
pid_t pid=fork();
if(pid == -1)E_MSG("fork", -1);
exit(-1);
// return -1;
}
$ ./a.out
bye...-1...hello
bye...-1...hello