进程
-
什么是程序、什么是进程,有什么区别?
- 程序是静态的概念,编译生成的可执行文件叫程序。进程是程序运行起来,系统就多了一个进程。
-
如何查看系统中有哪些进程?
- ps:显示当前进程的状态
- ps -aux:显示所有包含其他使用者的进程
- ps -aux| grep init(init是过滤关键字) :查找指定进程
- top:实时显示 process 的动态(也可显示进程信息,类似于windos平台下的任务管理器,可以查看进程资源的占用等等)
- ps:显示当前进程的状态
-
什么是进程标识符?
- 每一个进程都有一个非负整数表示唯一的ID,叫做PID,类似身份证
- PID=0:称为交换进程(swapper)(作用:进程调度)
- PID=1:init进程(作用:系统初始化)
- C程序里可以getpid()函数可以获取该C程序进程ID号(具体可以使用命令man 2 getpid查看)
-
什么叫父进程,什么叫子进程?
- A进程创建了B进程,那么B进程称A进程为父进程,A进程称B进程为子进程
-
C程序的存储空间是如何分配的?
- 程序运行时系统会分配一段内存,该内存的低内存地址从正文(代码段)开始,高地址(命令行参数和环境变量)
- 正文(代码段)
- 程序当中由基本语句形成的流程或是算法,如if else switch 这样的都是属于代码段
- 数据段
- 已初始化的数据段(.data):已初始化的变量属于数据段
- 未初始化的数据(.bss) :函数外未初始化的变量属于bss段
- 常量(.rodata):常量(宏定义,[const修饰过的变量还是变量,只是值不能被修改])
- 栈
- 函数调用,函数里的局部变量,函数产生的信息都存放在堆内存里边
- 命令行参数和环境变量(mian函数的形参:argc,argv)
- 堆
- malloc申请动态内存空间
-
父子进程间的存储空间的关系
-
-
早期Linux系统设计子进程会直接拷贝父进程的如下部分内容
- 正文、初始化的数据、未初始化的数据、堆、栈、命令行参数和环境变量、打开的文件、IO流等等
-
后期Linux系统可以进行写时拷贝(选择性的拷贝)
- 什么是写时拷贝?
- Linux 写时复制机制原理 0 在 Linux 系统中,调用 fork 系统调用创建子进程时,并不会把父进程所有占用的内存页复制一份,而是与父进程共用相同的内存页,而当子进程或者父进程对内存页进行修改时才会进行复制 —— 这就是著名的写时复制机制。
- 什么是写时拷贝?
-
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t pid; int a = 10; pid = fork(); if(pid > 0 ){ sleep(1); } else if (pid == 0){ a = a+100; } printf("a=%d\n",a);//父进程延时1S子进程先执行,父进程打印10,子进程打印110说明子进程拷贝了父进程的a=10,但是子进程修改a的值只限于子进程有效,父进程仍然是10 return 0; }
-
创建进程
-
如何创建一个进程?
-
pid_t fork(void);
-
返回值 说明 0 代表当前进程是子进程 非负数 代表当前进程为父进程 -1 调用失败 -
fork函数调用成功返回两次
-
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t pid; pid_t pid2; pid_t retpid; pid = getpid(); printf("before fork: PID = %d\n",pid); retpid = fork();//返回的是子进程PID pid2 = getpid(); printf("after fork: PID = %d\n",pid2); //严格意义上说并不是返回两次,而是返回值大于0意味着是父进程成功创建子进程fork返回子进程PID号,子进程会拷贝父进程的相关信息,拷贝给子进程的retpid被赋值为0(像是初始化为0一样) if(pid == pid2){ printf("This is father print,pid = %d\n",retpid); } else { printf("This is child print, pid = %d retpid=%dn",getpid(),retpid); } return 0; }
-
补充:
- vfork函数也可以创建进程,与fork有如下区别
- vfork直接使用父进程存储空间,不拷贝
- vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行
- vfork函数也可以创建进程,与fork有如下区别
-
创建子进程的目标
- 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中时常见的—父进程等待客户端的服务请求。当这种请求达到时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达
- 一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec
进程退出
- 正常退出
- Main函数调用return
- 进程调用exit(),标准c库
- 进程调用_exit() 或则 _Exit(),属于系统调用
- 进程最后一个线程返回
- 最后一个线程调用pthread_exit
- 异常退出
- 调用abor
- 当进程受到某些信号时,如Ctrl+c
- 最后一个线程对取消请求做出响应
-
-
退出函数
-
void exit(int staus);
-
void _exit(int staus);
-
void _Exit(int staus);
- 1函数是对2和3的封装
-
等待子进程
-
为什么要等待子进程退出?
- 创建子进程是希望子进程去”干活“,需要关心子进程”干活“干得怎么样,干完有干完的状态(exit(?)退出),没干完也有没干完的状态(abort,还是被杀死了?),父进程不仅要等待还要收集子进程退出的状态
- 子进程退出状态不被收集,变成僵死进程(僵尸进程)
-
等待函数
-
pid_t wait(int *status);
-
pid_t waitpid(pid_t pid,id_t *status,int options);
-
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);(用得比较少)
-
wait和waitpid的区别(但是status是一样的):
-
wait使调用者阻塞,waitpid有一个选项,可以使调用者不阻塞(使用waitpid收集子进程状态,子进程好像也会变成会僵尸进程)
-
-
-
-
status 说明 非空 子进程退出状态放在它所指向的地址中 空 不关心退出状态 -
-
-
解析退出状态
-
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> int main () { pid_t num; int a=0; int status=0; num =fork(); if(num == -1){ printf("making course file\n"); } else if(num == 0) { while(1){ printf("This is son course %d \n",getpid()); sleep(1); a++; if(a==3) { exit(3); } } } else if(num > 0) { wait(&status);//收集子进程退出状态 printf("son course exit status =%d\n",WEXITSTATUS(status));//使用宏WEXITSTATUS对退出状态进行解析才能得到exit退出状态3 while(1) { sleep(1); printf("This is father course %d \n",getpid()); printf("a=%d\n",a); } } return 0; }
-
-
-
什么是孤儿进程?
- 父进程如果不等待子进程退出,在子进程之前就结束了自己的”生命“,此时子进程叫做孤儿进程
- Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程