1.什么是进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。
2.进程的内存管理
1个进程总是占据4个gb的内存空间
前3个gb是该进程的独立内存空间,后1个gb是所有进程共享的内存空间
注意:linux系统中,进程使用的其实不是物理内存,而是虚拟内存
Linux 系统中的进程内存管理是通过将虚拟地址映射到物理内存来实现的。
4个gb全是虚拟内存,这4gb的虚拟内存,是由1gb的物理内存映射出来的
3.进程的组成部分
代码段 、数据段 、堆、栈和内存映射区域
4.进程编号:pid
每一个进程都有自己独立的编号,称为pid
pid的分配方式为:循序寻找未使用的pid
pid的最大值:
早些时候的linxu系统,使用一个short变量来管理的pid,所以pid的最大值就是32767,取值范围就是0~32767 (0,2^7-1)
但是,现在的linux系统,使用的是一个int类型数据来管理的pid,pid取值范围变成了0~65535(0,2^15-1)
5.3个特殊的进程0 1 2
0号进程
0号进程,通常也被称为idle进程,或者也称为swapper进程。
0号进程是linux启动的第一个进程,它的task_struct的comm字段为"swapper",所以也成为swpper进程。
#define INIT_TASK_COMM "swapper"
当系统中所有的进程起来后,0号进程也就蜕化为idle进程,当一个core上没有任务可运行时就会去运行idle进程。一旦运行idle进程则此core就可以进入低功耗模式了,在ARM上就是WFI。
1号进程
我们通常将init称为1号进程,其实在刚才kernel_init的时候1号线程已经创建成功,也可以理解kernel_init是1号进程的内核态,而我们所熟知的init进程是用户态的。
至此1号进程就完美的创建成功了,而且也成功执行了init可执行文件。
2号进程
2号进程,是由1号进程创建的。而且2号进程是所有内核线程父进程。
2号进程也叫kthread进程。
1 2 这3个进程本质上是一个进程,3个分身(线程)
总结:
1、linux启动的第一个进程是0号进程,是静态创建的
2、在0号进程启动后会接连创建两个进程,分别是1号进程和2和进程。
3、1号进程最终会去调用可init可执行文件,init进程最终会去创建所有的应用进程。
4、2号进程会在内核中负责创建所有的内核线程
5、所以说0号进程是1号和2号进程的父进程;1号进程是所有用户态进程的父进程;2号进程是所有内核线程的父进程。
6.进程的分类
1、交互进程: 由一个shell启动的进程,交互进程既可以在前台运行,也可以在后台运行。
2、批处理进程:这种进程和终端没有联系,是一个进程序列。
3、监控进程: 也称守护进程,是一个在后台运行且不受任何终端控制的特殊进程,用于执行特定的系统任务。
7.进程相关的shell指令
1、ps -ef 显示进程之间关系的指令
UID PID PPID C STIME TTY TIME CMD root 1 0 0 08:58 ? 00:00:05 /sbin/init splas
UID:用户的id
PID:当前进程的id
PPID:父进程id
C:该进程被连接的数量
STIME:开始运行的时间
TTY:该进程所依赖的终端,
?表示不依赖任何终端
CMD:进程名
2、ps -ajx 查看进程状态的指令
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 0 1 1 1 ? -1 Ss 0 0:05 /sbi。。。。。。。
PGID:进程组的编号
TPGID:进程的类型,-1表示的是守护进程,>0表示交互进程
STAT:进程的运行状态,进程运行状态可以使用指令 man ps 查看
3、ps -aux 显示进程占据cpu资源情况
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.3 0.3 159812 9152 ? Ss 10:38 0:06 /sbin/init splash
%CPU : CPU的占有率
%MEM : 内存占有率
4、向进程发送信号的指令:kill
例如:kill -9 进程号,其中kill是发送信号的意思, -9 才是杀死信号
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX 1~31:标准信号 34~64:实时信号
5、pidof 进程名
获取一个进程的pid
6、top
显示任务管理器:动态的显示系统中的所有进程
7、pstree
以树状图的形式,显示系统中所有进程关系
8、ps ef -o pid,ppid,command
只显示系统中所有进程的pid,ppid,和进程名
8.进程状态的切换
进程五态
9.如何创建一个进程,多进程编程的时候,会发生什么
pid_t fork(void);
功能:fork函数的功能就是复制当前进程,在内核进程表中创建一个新的进程表象,该进程称为子进程,被复制的进程称为父进程。
- 子进程的代码和父进程的完全相同,同时它还会复制父进程的数据(堆数据、栈数据和静态数据)。数据的复制采用的是写时复制。此外,创建子进程后,父进程打开的文件描述符默认在子进程中也是打开的,且文件描述符的引用计数+1。
常用的一些函数
pid_t getpid(void);
功能:返回调用者进程的进程编号
pid_t getppid(void);
功能:返回调用者进程的父进程的进程编号
通过fork返回值去区分父子进程
pid_t pids = fork();
if (pids > 0) {
// 父
}else if (pids == 0){
// 子
}else{// 失败
perror("fork");
}
10.孤儿进程和僵尸进程
回收子进程
pid_t wait(int *stat_loc);
功能:wait函数会获取等待回收资源的子进程的状态信息,并将获取到的状态信息保存到 stat_loc指向的内存空间中,然后回收子进程的资源.
如果不需要获取子进程死亡时候的信息的话,stat_loc参数直接传NULL或者0
注意:wait是一个阻塞型函数
void exit(int status);
功能:立刻技术当前进程,并且参数status作为该进程main函数的返回值进程返回
pid_t waitpid(pid_t pid, int *stat_loc, int options);
这个函数是wait函数的高阶自定义版本,允许自定义很多细节部分
参数pid:
pid == -1 : 可以回收任意子进程的资源
pid > 0 : 回收一个进程的资源,回收传入的pid进程的资源,但是要求该pid进程所在的进程组和父进程是一样的
pid == 0 : 回收任意进程组编号和父进程一样的子进程的资源
pid < -1 : 回收一个进程的资源,进程号为 |pid|那个进程的资源
参数options:一般有2个选项
0 :waitpid函数为阻塞型函数
WNOHANG :waitpid函数为非阻塞型函数
waitpid(-1,0,0) ======wait(0)
僵尸进程
当子进程死亡,而父进程一直没有空去wait子进程的资源,此时该子进程资源就泄漏了,这样的子进程,我们称为僵尸进程
注意:僵尸进程是一个高危操作,尽量避免僵尸进程的出现
11.守护进程
守护进程:是一个不与任何用户交互,不依赖任何进程组,独立存在的,在后台持续运行的一个服务性质的进程
① 成为一个孤儿进程,脱离终端控制(这是初步脱离终端控制)
② 成为一个新的进程组的组长
③ 切换守护进程的工作目录,到系统根目录
④ 设置守护进程的掩码为 0111
⑤ 彻底脱离与终端的联系:将守护进程的标准输入,输出,错误流全都重定向到独立的文件中去
代码如下:
// 1 子进程成为孤儿进程 pid_t pid_n = fork(); if (pid_n == -1) { perror("fork"); return 1; } if(pid_n > 0) {return;} // 2 成为新的进程组组长 if (setsid() == -1) { perror("setsid"); return 1; } // 3 切换目录到根 if (chdir("/") == -1) { perror("chdir"); return 1; } // 4 设置掩码为0111 umask(0111); // 5 脱离终端(stdio、in、err) int fp = open("01test.c", O_RDWR | O_CREAT); if (fp == -1) { perror("open"); return 1; } if (dup2(fp, 0) == -1 || dup2(fp, 1) || dup2(fp, 2)) { perror("dup2"); return 1; } while (1) { // 守护干活 }
12.练习
/*
创建一对父子进程:
父进程负责向文件中写入 长方形的长和宽
子进程负责读取文件中的长宽信息后,计算长方形的面积
*/
int width; int height; void scranf_width(); void scranf_height(); void scranf_width(){ printf("请输入长方体宽度(整数):"); if (scanf("%d",&width) == 1) { printf("请输入长方体高度(整数):"); return; } printf("输入不合法清重新输入\n"); while (getchar() != '\n') { break; } scranf_width(); } void scranf_height(){ if (scanf("%d",&height) == 1) { return; } printf("输入不合法清重新输入\n"); while (getchar() != '\n') { break; } printf("请输入长方体高度(整数):"); scranf_height(); } int main(int argc, char const *argv[]) { pid_t pids = fork(); if (pids > 0) { // 父 int np = open("homework.txt",O_CREAT | O_WRONLY | O_TRUNC, 0666); if (np == -1) { perror("open"); return 1; } scranf_width(); // 获取宽度 write(np, &width, sizeof(int)); scranf_height(); // 获取高度 write(np, &height, sizeof(int)); close(np); printf("计算中\n"); wait(NULL); }else if (pids == 0){ // 子 while (1) { // 循环遍历读取文件 sleep(1); // 增加阻尼,防止频繁调用 int c_np = open("homework.txt",O_RDONLY); if (c_np == -1) { perror("open"); return 1; } if (read(c_np, &width, sizeof(int)) != 0 && read(c_np, &height, sizeof(int)) != 0) { printf("计算结果:长方体的宽:%d,高:%d,面积是:%d\n", width, height, width*height); close(c_np); break; }else{ close(c_np); } } }else{ perror("fork"); } return 0; }