Day7
基本概念
-
进程与程序
- 程序:存储在磁盘上的文件,包含可执行指令和数据的静态实体
- 进程:运行中的程序(一个程序可以执行多次,加载出多个进程),进入活动状态的计算机程序。
-
进程的分类:
- 交互进程:有输入,输出,用户可以根据自己的情况输入数据,得到想要的结果(一般进程)。
- 批处理进程:由脚本加载执行的程序(Linux下的shell,win下的bat)
- 守护进程:总是活跃的,后台运行,一般由系统开机时加载执行或root用户加载执行。
-
查看进程
- 简单方式:
ps
,显示出当前用户有终端控制权的进程信息 - 列表形式:
ps -aux
,以列表形式显示详细信息a
所有用户的终端控制进程x
所有用户的无终端控制的进程u
详细方式显示
- 简单方式:
-
进程的详细信息列表
USER
进程的用户PID
进程的id%CPU
进程的cpu使用率%MEM
内存的使用率VSZ
占用虚拟内存的大小RSS
占用物理内存的大小TTY
终端的次设备号,如果无终端控制显示?STAT
进程的状态O
就绪态,等待被系统调度R
运行态,Linux无就绪态,归于RS
休眠态,可以被系统中断(信号)唤醒转入运行态T
暂停态,是被SIGSTOP信号暂停,由SIGCONT信号转入运行态Z
僵尸态,已经结束停止运行,但父进程还没有回收<
高优先级进程N
低优先级进程l
多线程化的进程+
在前台进程组中的进程s
会话首进程
START TIME
进程的开始时间COMMAND
程序可执行文件名
-
父进程与子进程,孤儿进程与僵尸进程
一个进程A可以创建出另一个进程B,创建者叫fu进程,被创建进程叫子进程,父进程启动子进程后,在操作系统的调用下父进程同时执行(同步)。
如果子进程先于父进程结束,会向父进程发送SIGCHLD信号,父进程收到信号后,就应该去回收子进程的相关资源,但在默认情况下,父进程忽略该信号
当子进程结束后,父进程没有回收子进程的资源,子进程变成了僵尸进程
如果父进程先于子进程结束,子进程就变成了孤儿进程,同时被孤儿院收养(init),然后就变成了init 的子进程
-
进程标识符
操作系统会为每个进程分配一个唯一的标识符,采用无符号整数表示,即进程ID
进程ID在任何时候都是唯一的,但是可以重用,当一进程结束,新创建的进程才可以使用它的进程ID(延时重用)
- getpid 获取进程ID
- getppid 获取父进程ID
- getuid 获取当前进程的用户ID
- getgid 获取当前进程组ID
pid_t fork(void);
- 创建一个新进程
- 返回值 一次调用两次返回,失败返回-1(当进程数超出系统限制,创建失败)
- 两次返回分别是子进程ID和0,父进程会拿到子进程的ID,子进程返回0,借此可以分别出父子进程0
- 通过fork创建的子进程就是父进程的副本(拷贝)
- 子进程会获取父进程的数据段,bss段,堆,栈,IO流(共享文件指针和文件描述符),缓冲区拷贝,与父进程共享代码段。
- 子进程会继承父进程的信号处理方式
- fork函数调用后父子进程各自执行,谁先返回不一定,但可以使用一些手法来确保谁先执行
- 僵尸进程与孤儿进程的实现
#include <sidio.h>
#include <unistd.h>
int main(){
pid_t id = fork();
if(id == 0){
//子进程分支
printf("我的子进程%u,我的父进程是%u\n",getpid(),getppid());
}else{
//父进程分支
printf("我是父进程%u,我的子进程是%u\n",getpid(),id);
}
}
int main(){
printf("*");
fork();
printf("*");
fork();
}
练习1 实现一个程序来验证子进程确实拷贝了父进程的数据段,bss段,堆,栈,IO流
练习2 为一个父进程创建5个子进程,一共六个进程
进程的正常退出
-
从main函数中return
-
调用标准库中的exit函数
void exit(int status);
- 调用者立即结束该进程
- status 退出代码,可以在父进程中获取到,子进程留给父进程的遗言
- 推出前做的事
-
先调用事先注册的函数(通过atexit/on_exit)
int on_exit(void (*function)(int , void *), void *arg);
- function 函数指针,无返回值,参数1为exit函数的参数,参数2,为on_exit函数的第二个参数
- arg 传给function的第二个参数
int atexit(void (*function)(void));
- 注册一个函数,当进程通过exit函数开始结束时调用
- function 函数指针,无返回值无参数
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void onfunc(int num, void* arr){ printf("%s %d %s\n",__func__,num,(char*)arr); } void atfunc(void){ printf("我要死了。。\n"); } int main(){ on_exit(onfunc,"silesile..."); atexit(atfunc); exit(10); }
-
冲刷所有处在未关闭状态的标准IO流,删除所有临时文件
-
返回一个整数(EXIT_SUCCESS/EXIT_FAILURE)给操作系统
-
该函数不会返回,它的功能借助了_exit/_Exit函数
-
-
调用
_exit/_Exit
函数退出- 这两个函数的功能是一样的
#include <unistd.h> void _exit(int status); #include <stdlib.h> void _Exit(int status);//调用系统的_exit
- 调用的进程会结束,没有返回值
- status 会被父进程获取到(低八位,一个字节)
-
进程结束前会关闭所有处于打开状态的文件描述符
-
把所有子进程托付给孤儿院(init)
-
向它的父进程发送SIGCHLD信号
注意 exit函数也执行以上操作,因此它底层调用了_exit/_Exit
-
-
进程的最后一个线程执行最后一条语句
-
进程的最后一个线程调用了pthread_exit函数
进程的异常退出
- 调用了abort函数,该函数会产生SIGABRT信号
- 进程接收到一些信号(无设捕获处理,或无法捕获处理)
- 进程的最后一个线程接收到"取消"请求,并作出响应,相当于线程接收到了结束信号
wait/waitpid
-
pid_t wait(int *wstatus);
- 等待所有的子进程结束,并获取到最终的状态码,只要有一个进程结束就立即返回
- 应该是父进程收到子进程发来的SIGCHLD信号时,调用wait函数回收子进程的资源比赛获取结束状态
- 如果所有子进程都在运行,则wait阻塞
- 如果已有僵尸进程,wait也会立即返回,回收资源获取结束状态码
- 如果没有子进程,则返回失败-1
-
pid_t waitpid(pid_t pid, int *wstatus, int options);
- 等待指定的进程结束,并获取到最终的状态码
- pid
- -1 等待任一子进程结束,此时与wait等价
- >0 等待进程号为pid的进程结束,此时只等待一个进程结束
- =0 等待同组的任意一子进程结束,此时等待的是整个进程组
- <-1 等待的是进程组id是pid的绝对值中的任一子进程结束,此时等待的是整个进程组
- options
- WNOHANG 非阻塞模式,如果没有子进程结束则立即退出
- WUNTRACED 如果子进程处理暂停,则返回它的状态
- WCONTINUED 如果子进程从暂停转为继续,则返回它的状态
- wait函数只能孤独地等待子进程结束,而waitpid可以有更多的选择
- waitpid不光可以等待子进程,也可以等待同组进程
- waitpid可以阻塞也可以不阻塞
- 也可以监控子进程的暂停或继续状态
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void sigchld(int sig){
int status = 0;
pid_t id = wait(&status);//waitpid
printf("我的孩子%u,去世了...5555!!!,它的遗言是%d\n",id,WEXITSTATUS(status));
}
int main(){
signal(SIGCHLD,sigchld);
if(fork()){
printf("我是进程%u\n",getpid());
pasue();
}else{
sleep(3);
printf("我是进程%u\n",getpid());
return
}
}
vfork
pid_t vfork(void);
- 功能 与fork的功能基本一致
- 区别 通过vfork创建的进程不复制父进程的的地址空间,必须通过excl系列函数加载自己的可执行程序
- 当执行vforks时,子进程先返回,此时它占用了父进程的地址空间,当子进程成功创建后(通过excl加载可执行程序).父进程才返回
execl
- 加载子进程的可执行文件
int execl(const char *path, const char *arg, ...);
- path 可执行文件的路径
- arg 第一个main函数的参数,最后一次必须以NULL结尾
int execlp(const char *file, const char *arg, ...);
- file 可执行文件的名字,会从PATH环境变量表的路径中查找可执行文件并执行
int execle(const char *path, const char *arg, ..., char * const envp[]);
- envp 父进程的环境变量表,传递给子进程
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 main(){
pid_t id = vfork();
if(id){
printf("我是子进程%u,我的父进程是%u\n",getpid(),getppid());
execl("./a.out","a.out",NULL);
}else{
printf("我是父进程%u,我的子进程是%u",getpid(),id);
pause();
}
}
system
int system(const char *command);
- 执行系统命令的,也可以加载可执行程序
- 相当于创建了一个子进程,但子进程不结束,该函数不返回,父子进不会同时执行
- command
#include <stdio.h>
#include <stdlib.h>
int main(){
system("./a.out");
printf("我是父进程\n");
pause();
}
练习 实现system函数
进程组
- 是一个或多个进程的集合,每个进程除有一个进程ID还有一个进程祖ID,进程组中的进程归属同一个作业控制(负责完成同一个任务)
- 每个进程都有一个组长,组长的进程ID就是组ID
- 同一进程组的进程,会统一接收到终端的信号,由fork创建的子进程,默认加入父进程的进程组
pid_t getpgid(pid_t pid);
- 获取pid进程的进程组id
int setpgid(pid_t pid, pid_t pgid);
- 设置进程pid的进程组id