Linux基础和系统编程 下
进程和程序以及CPU有关
1.进程是运行起来的程序,进程要占用系统资源
进程要占用内存,执行计算指令要占用CPU,总线
2.程序是死的,程序占磁盘空间
3.一个可执行文件可以在多个终端中运行
4.程序能看到,进程看不到
5.并发:在操作系统中,一个时间段中有多个进程都处于已启动到运行完毕的状态
6.单道程序设计:所有进程一个一个排队,若A阻塞,B只能等待,即使CPU处于空闲状态。在人机交互时阻塞的出现是必然的。所有这种模型在系统资源利用上都及其不合理,存在不就就淘汰了。
比如微软的道斯系统
7.多道程序设计:在计算机内存中存放几道相互独立的程序,它们在管理程序的控制下,相互穿插的运行,多道程序设计需要硬件作为保证。
8.单核CPU也能并发,A进程完成一点,未全部完成,B开始运行,B没有完成,C开始运行
通过时钟中断来执行
9.存储介质
寄存器
cache(缓存)
内存(电信号)
硬盘(物理方法读取)
网络(几乎可以认为存储是无线的)
从上到下存储量越来越大,速度越来越慢
一个寄存器是4096字节,4kb
10.CPU和MMU
CPU由ALU算数逻辑单元(只会算加减),寄存器堆,译码器,预取器组成
MMU是虚拟内存映射单元,在CPU内部
虚拟内存和物理内存映射关系
1.程序不占用内存,进程占用内存
2.kernel有pcb进程控制块,有一些成员变量,进程描述符
3.
虚拟地址(真正的物理地址在内存条上)./a.out
./b.out
想把虚拟地址放在内存条上就需要MMU
如果两个进程虚拟内存地址一样,分块映射
4.一个page是4kb,MMU是4kb,一个寄存器是4kb
5.当申请的内存过大,需要连续空间时,MMU需要映射多个内存条区域
6.操作系统只有一个,所有进程的kernel共享,所以所有进程位于kernel的pcb都映射到内存条的一块区域内
7.MMU可以修改访问级别,CPU会将内存分级(Windows分4级,Linux分2级),MMU可以进行权级切换
pcb进程控制块
1.一个寄存器只有4Byte(32bit)
2.进程控制块:每个进程在内核中都有一个进程控制块来维护进程相关信息,Linux内核的进程控制块是task struct结构体
/user/src/linux-headers-3.16.0-30/include/linux/sched.h文件可以查看struct task_struct结构体的定义
3.需要掌握
进程id:每个进程有唯一的id
进程的状态:初始,就绪(等待CPU分配时间片),运行,挂起(等待除CPU以外的其他资源主动放弃CPU),停止
进程切换时需要保存和恢复的一些CPU寄存器(CPU正在计算,时间片结束了,需要将这个进程的寄存器数据存起来,再次运行这个进程时将值放回寄存器)
描述虚拟地址空间的信息
描述控制终端的信息
当前工作目录位置
umask掩码(不同进程有不同的umask掩码)
文件描述符表,包含很多指向file结构体的指针
和信号相关的信息
用户id和主id
会话Session和进程组
进程可以使用的资源上限
ps aux查询进程,第二列PID就是进程
环境变量
LD_LIBRARY_PATH动态连接器
PATH记录可执行文件目录位置
echo $PATH可以将这个环境变量打印
SHELL解析命令
TERM当前终端类型
LANG语言和locale,决定字符编码以及时间,货币等信息的显示格式
HOME当前用户的宿主目录
Fork函数原理
1.创建一个子进程
返回值为整数没有参数
2.父进程有的子进程都有,子进程执行fork();下面的
成功子进程会给父进程返回一个0,父进程返回子进程的id
Fork创建子进程
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<pthread.h> int main() { • printf("before fork--1-------\n"); • printf("before fork--2-------\n"); • printf("before fork--3-------\n"); • printf("before fork--4-------\n"); • printf("before fork--5-------\n"); • pid_t pid=fork(); • if(pid==-1) • { • perror("fork error"); • exit(1); • } • else if(pid==0) • { • printf("-------child is created\n"); • } • else if(pid>0) • { • printf("-------parent process:my chid is%d\n",pid); • } • printf("===========end of file\n"); • return 0; }
before fork 1,2,3,4执行一次
===========end of file执行两次,因为fork之后的子进程和父进程都会执行
getpid和getppid
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<pthread.h> int main() { printf("before fork--1-------\n"); printf("before fork--2-------\n"); printf("before fork--3-------\n"); printf("before fork--4-------\n"); printf("before fork--5-------\n"); pid_t pid=fork(); if(pid==-1) { perror("fork error"); exit(1); } else if(pid==0) { printf("-------child is created,pid=%d\n,parent_pid=%d",getpid(),getppid()); } else if(pid>0) { printf("-------parent process:my chid is%d,mypid=%d,myparent_pid=%d\n",pid,getpid(),getppid()); } printf("===========end of file\n"); return 0; }
ps aux | grep 7876
父进程的父进程是bash
循环创建N个子进程
1.一个fork函数调用可以创建一个子进程,那么创建N个子进程应该如何实现
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<pthread.h> int main() { int i; for(i=0;i<5;i++){ pid_t pid=fork(); //每次所有父进程,子进程,子进程产生的子进程都会产生一个子进程 if(pid==-1) { perror("fork error"); exit(1); } else if(pid==0) { printf("-------child is created,pid=%d\n,parent_pid=%d",getpid(),getppid()); } else if(pid>0) { printf("-------parent process:my chid is%d,mypid=%d,myparent_pid=%d\n",pid,getpid(),getppid()); } } return 0; }
2.
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<pthread.h> int main() { int i; for(i=0;i<5;i++){ if(fork()==0) break; } if(i==5) printf("I'm parent"); else printf("I'm %dth child",i+1); return 0; }
每个进程会争取cpu,导致顺序不一致
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<pthread.h> int main() { int i; for(i=0;i<5;i++){ if(fork()==0) break; } if(i==5) { sleep(5); printf("I'm parent"); } else{ sleep(i); printf("I'm %dth child",i+1); } return 0; }
父子进程共享哪些内容
1.父子进程之间在fork之后
相同处:全局变量,data,text,栈,堆,环境变量,用户ID,宿主目录,进程工作目录,信号处理方式
不同处:进程Id,fork返回值,父进程id,进程运行时间,闹钟(定时器),未决信号集
似乎,子进程复制了父进程0~3G用户内容,以及父进程的PCB,但是pid不同,实际不是,父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销
2.躲避父子进程共享全局变量的知识误区
3.共享:1.文件描述符(打开文件的结构体) 2.mmap建立的映射区
父子进程gdb调试
1.使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默 认跟踪父进程
2.set follow-fork-mode child命令设置gdb在fork之后跟踪子进程
3.set follow-fork-mode parent跟踪父进程,默认父进程
exec函数族原理
1.fork创建子进程后执行的是和父进程相同的程序(但是可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
2.进程还是父进程的子进程,但是执行的内容不一样了,exec调用的部分不会返回给父进程,进程id不变
-execlp和execl函数
execlp函数
加载一个进程,借助PATH环境变量
该函数通常调用系统程序。如:ls,date,cp,cat等命令
int execlp(const char *file,const char *arg,...);
execlp("ls","-l","-d)
失败返回-1
int execl(const *path,const char *arg,...);
指定路径
execl("/bin/ls","ls","-l","-F",NULL);
exec函数族的特性
1.execvp函数
加载一个进程
int execvp(const char *file,char *const argv[]);
char *argv[]={"ls","-l","-h",NULL};
execvp("ls",argv);
2.exec函数一旦调用成功即执行新的程序,不返回,只有失败才会返回,错误值-1。所以通常直接在exec函数调用后直接调用perror()和exit(),无需if判断
孤儿进程和僵尸进程
孤儿进程
父进程先于进程结束,则子进程称为孤儿进程。子进程的父进程成为init进程,称为init进程领养孤儿进程。
orphan.c
ps aux
僵尸进程
进程终止,父进程尚未回收,子进程残留资源(PCB)存放在内核,变成僵尸进程。
PCB标记子进程死亡原因
[zoom]<defunct>
wait回收子进程
1.一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但是它的PCB还保留着,内核在其中保存了一些信息,如果是正常终止则保存退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。一个进程的退出状态可以在shell中用特殊变量$查看因为shell是他的父进程,当它终止时Shell调用wait或waitpid得到它退出的状态同时彻底清除这个进程
2.父进程调用wait函数可以回收子进程的终止信息,该函数有三个功能: 1.阻塞等待子进程退出
2.回收子进程残留资源
3.获取子进程结束状态(退出原因)
3.pid_t wait(int *status);
pid_t waitpid(pid_t pid,int *status,int *options);
成功返回子进程的进程id,失败-1
获取子进程退出值和异常终止信号
1.使用宏函数
1.if(WIFEXITED(status))
为真说明子进程正常终止
WEXITSTATUS(status);
子进程返回值
2.WIFSIGNAL(status)
为真,异常终止
WTERMSIG(status)
取得使进程终止的那个信号的编号
2.所有进程终止都是信号
Linux基础命令学习资源
【麻省理工学院】MIT 计算机操作环境导论 Missing Semester (超强的计算机工具课)-_哔哩哔哩_bilibili
黑马程序员新版Linux零基础快速入门到精通,全涵盖linux系统知识、常用软件环境部署、Shell脚本、云平台实践、大数据集群项目实战等_哔哩哔哩_bilibili