进程
- 当一个进程正常终止时可以通过
int atexit(void(*function)(void))
注册进程终止处理函数
PART1——进程相关概念
进程是一个动态的过程,而非一个静态的文件,是程序的一次运行过程,当应用程序被加载到内存中运行之后才能称为一个进程
1.1 进程号
每一个进程都有一个进程号(PID),进程号是一个正数,用于唯一标识一个进程。
在应用程序中可以
- 通过
pid_t getpid(void)
来获取进程号 - 通过
pid_t getppidd(void)
获取父进程的进程号
1.2 进程环境变量
每一个进程都有一组与其相关的环境变量,这些环境变量是由键值对组成的
在应用程序中可以
- 通过申明
extern char **environ
就可以使用environ[]
来获取环境变量 - 通过
char *getenv(const char *name)
来获取指定环境变量 - 通过
int putenv(char *string)
添加环境变量 - 通过
int setenv(const char *name,const char *value,int overwrite)
添加或者修改环境变量overwrite
为0,不修改原有值,非零,则覆盖
- 通过
unsetenv(const char *name)
移除环境变量 - 通过
int clearenv(void)
清空环境变量
1.3 进程的内存布局
代码段
具有只读属性,代码段是可以共享的,即使在多个进程也可以运行同一段程序初始化数据段
已经初始化的全局变量和静态变量,当程序加载到内存中是,从可执行文件中读取这些变量值未初始化数据段
未初始化的全局变量和静态变量,也称为bss段,在程序开始执行之前,系统会将本段的所有内存初始化为0,可执行文件未给bss段分配存储空间,只记录bss段的位置及其所需大小,直到程序运行时,由加载器分配这一段的内存空间栈
函数内部的局部变量以及函数调用所需保存的信息都放在栈区中,函数传递的实参UI及函数的返回值也存放在此堆
可在运行时动态进行内存分配的一块区域- 可以通过
size+二进制可执行文件
来查询文本段、数据段和bss段的段大小
1.4 进程的虚拟地址空间
32位的系统,每个进程的逻辑地址空间为4GB,但是实际的物理地址空间并没有那么大,所以需要通过一个MMU系统来进行地址映射
为什么要用虚拟地址?
- 内存使用效率低
- 进程地址空间不隔离
- 无法确定程序的链接地址
- 进程与进程、进程与内核之间相互隔离
- 两个或更过的进程能够共享内存
- 便于实现内存保护机制
- 无需关心链接地址
1.5 进程的诞生与终止
1.5.1 诞生
- 一个进程可以通过 fork()或 vfork()等系统调用创建一个子进程,一个新的进程就此诞生
- 上图中进程号为 1 的进程便是所有进程的父进程,通常称为 init 进程,它是 Linux 系统启动之后运行的第一个进程,它管理着系统上所有其它进程, init 进程是由内核启动,因此理论上说它没有父进程
1.5.2 终止
-
正常终止:
exit()
_exit()
_Exit()
-
异常终止:调用
abort
异常终止,或者,接收到某些信号导致异常终止 -
exit()函数和 exit()函数的 status 参数定义了进程的终止状态,父进程可以调用 wait()函数以获取该状态。
-
exit()函数会比_exit()会多做一些事情,包括执行终止处理函数、刷新 stdio 流缓冲以及调用_exit(),在前面曾提到过,在我们的程序当中,父、子进程不应都使用 exit()终止,只能有一个进程使用 exit()、而另一个则使用_exit()退出,当然一般推荐的是子进程使用_exit()退出、而父进程则使用 exit()退出。
1.6 进程的状态与关系
进程的状态分为:
- 就绪态
- 运行态
- 僵尸态
- 可中断睡眠状态
- 暂停态
PART2——创建进程
应用程序可以
- 通过
pid_t fork(void)
创建一个新的进程,调用fork函数的进程称为父进程 - 每个进程都会从fork函数的返回出继续执行,会导致调用fork返回两次值,可以通过返回的两个值来区分子进程(返回值为0)还是父进程(返回值非零),如果返回-1,则创建失败
- fork()调用成功后,子进程、父进程各自在自己的进程空间中运行。事实上,子进程是父进程的一个副本, 譬如子进程拷贝了父进程的数据段、堆、栈以及继承了父进程打开的文件描述符,父进程与子进程并不共享这些存储空间,这是子进程对父进程相应部分存储空间的完全复制,
- 执行 fork()之后,每个进程均可修改各自的栈数据以及堆段中的变量,而并不影响另一个进程。
- 子进程是父进程的一个副本,但是对于程序代码段(文本段)来说, 两个进程执行相同的代码段,
- 代码段是只读的, 也就是说父子进程共享代码段,在内存中只存在一份代码段数据。
- 子进程拷贝了父进程的文件描述符表,使得父、子进程中对应的文件描述符指向了相同的文件表, 也意味着父、子进程中对应的文件描述符指向了磁盘中相同的文件,因而这些文件在父、子进程间实现了共享
fork使用场景
- 父进程希望子进程复制自己(父进程等待客户端的请求,当接收到请求,创建子进程去处理)
- 一个进程要执行不通的程序(app1中调用app2)
fork之后,父进程和子进程运行的先后顺序是随机的,可以通过信号来阻塞,之后再释放来区分先后
static void sig_handler(int sig)
{
//deal with signal
}
int main(void)
{
struct sigation sig = {0};
sigset_t wait_mask;
sigemptyset(&wait_mask);
sig.sa_handler=sig_handler;
sig.sa_flags=0;
if (sigation(SIGUSR1,&sig,NULL))
{
perror("error");
exit(-1);
}
switch (fork())
{
case -1:
perror("error");
exit(-1);
case 0://子进程
sleep(2);
kill(getppid(),SIGUSR1);
_exit(0);
default://父进程
if (-1 != sigsuspend(&wait_mask))
{
exit(-1);
}
exit(0);
}
}
PART3——监视子进程
3.1 wait
- 应用程序通过调用
pid_t wait(int *statue)
等待进程的任一子进程终止,同时获取子进程的终止状态信息 - 调用 wait()函数,如果其所有子进程都还在运行,则 wait()会一直阻塞等待,直到某一个子进程终止;
- 如果进程调用 wait(),但是该进程并没有子进程, 也就意味着该进程并没有需要等待的子进程, 那 么 wait()将返回错误,也就是返回-1、并且会将 errno 设置为 ECHILD
- 如果进程调用 wait()之前, 它的子进程当中已经有一个或多个子进程已经终止了,那么调用 wait()
也不会阻塞。 wait()函数的作用除了获取子进程的终止状态信息之外,更重要的一点,就是回收子
进程的一些资源
3.2 waitpid
- 应用程序可以通过调用
pid_t waitpid(pid_t pid,int *status,int options)
来等待指定进程终止
3.3 僵尸进程与孤儿进程
- 父进程先于子进程结束,此时子进程就变成一个孤儿进程。所有的孤儿进程都自动成为init(PID=1)进程的子进程
- 当子进程结束,而父进程还没有回收子进程的资源时,此时的子进程被称为僵尸进程
SIGCHLD信号
- 当父进程的某个子进程终止时,父进程会受到SIGCHLD信号
- 当父进程的某个子进程因收到信号而停止或者回复时,内核也可能向父进程发送该信号
PART4——调用新程序
4.1 exec族
系统调用 execve()可以将新程序加载到某一进程的内存空间,通过调用 execve()函数将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main()函数开始执行
int execl(const char *path,const char *arg,...)
int execlp(const char *file,const char *arg,...)
int execle(const char *path,const char *arg,...)
int execv(const char *path,char *const argv[])
int execvp(const char *file,char *const argv[])
int execvpe(const char *file,char *const argv[],char *const envp[])
int execve(const char *filename,char *const argv[],char *const envp[])
filename
指向需要载入当前进程空间的新程序的路径名argv
传递给新程序的命令行参数envp
指向了新程序的环境变量列表- 调用成功永不返回,一旦函数返回,就表明发生了错误
4.2 system
int system(const char *command)
使用示例:
system("ls -la")
进程间通信
1.管道
- 普通管道
- 流管道
- 有名管道