Linux发展历史
1969 --unix 肯汤姆森 , C 丹尼斯里奇
商业:
- IBM
- APPLE
- 惠普
- sun
BSD:
- freeBSD
Linux:
- redhat centos
- debain ubuntu
MMU作用
虚拟内存和与物理内存的映射
设置修改内存的访问级别
访问级别:0 1 2 3 (由高到低) 由MMU设置访问内存访问级别 用户空间3级 内核空间0级
虚拟空间常用区域:
|=========
|
|内核区
|
|=========
|环境变量、命令行参数
|stack
|共享库
|.data .bss rw
|.text .rodata ro
|.head
|=========
用户空间:
进程间独立:单独开辟空间,MMU映射到不同空间
内核控件:
映射同一块空间
PCB 进程控制块
1、进程id:系统中每个进程都有一个id C语言中使用pid_t 类型描述
2、进程状态: 停止态 就绪态 运行态 挂起态 终止态 保存在PCB中
3、进程切换时需要保存和恢复的一些CPU寄存器
4、描述虚拟地址空间的信息 虚拟地址和物理地址对应信息
5、描述控制终端的信息
6、当前工作目录
7、umask掩码
8、文件描述符表,包含很多指向file结构体的指针
9、和信号相关的信息
10、用户id和组id
11、会话(Session)和进程组
12、进程可以使用的资源上限 ()
环境变量
环境变量 是指操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:
1、环境变量一定是字符串
2、环境变量有统一的格式:[名]=:值 多个值用冒号分割
3、值用来描述进程环境信息
例如 输入date 由命令解析器调用 /bin/date
$PATH $SHELL $HOME $LANG $TERM 常用环境变量
环境变量的存储位置 和 命令行参数存储位置相近
存储形式 : char environ; 以NULL 作为哨兵结尾 使用 extern char environ;导出环境变量表
环境变量相关的函数
getenv() char getenv(const char name) 成功返回环境变量的值,失败返回NULL name不存在
setenv() int setenv(const char name, const char value, int overwrite); 成功返回0 错误返回-1
unsetenv() int unsetenv(const char* name); 删除环境变量
进程控制
函数fork()
pid_t fork(void) 创建一个子进程 读时共享,写时复制
返回值:普通函数一次调用返回一个值,
但是调用一次fork()返回俩个值:
1、返回紫禁城的pid (非负整数 > 0) 父进程的返回
2、返回0 子进程的返回
3、-1 时出错了
原理产生两个进程时 进程继续向后走; 父进程的fork返回子进程id 子进程返回0值
fork调用 一个进程 -> 两个进程
getpid()获取进程id getppid()获取父进程id 保证父进程晚点结束否则子进程会成为孤儿进程由root进程领养此时这个进程的父进程的id为1
进程共享
刚fork之后
父子相同处:全局变量,.data,.text,栈、堆、环境变量、用户id、宿主目录、进程工作目录、信号处理方式
父子不同处:进程id、fork返回值、父进程id、进程运行时间、闹钟(计时器)、未决信号集
读时共享,写时复制
父进程和子进程哪个先执行不确定取决于内核使用的调度算法
gdb调试
指令大全
============================================
run r 运行一个待调试的程序
continue c 让暂停的程序继续运行
next n 运行到下一行
step s 单步执行,遇到函数会进入
until u 运行到指定行停下来
finish fi 结束当前调用函数,回到上一层调用函数处
return return 结束当前调用函数并返回指定值,到上一层函数调用处
jump j 将当前程序执行流跳转到指定行或地址
print p 打印变量或寄存器值
backtrace bt 查看当前线程的调用堆栈
frame f 切换到当前调用线程的指定堆栈
thread thread 切换到指定线程
break b 添加断点
tbreak tb 添加临时断点
delete d 删除断点
enable enable 启用某个断点
disable disable 禁用某个断点
watch watch 监视某一个变量或内存地址的值是否发生变化
list l 显示源码
info i 查看断点 / 线程等信息
ptype ptype 查看变量类型
disassemble dis 查看汇编代码
set args set args 设置程序启动命令行参数
show args show args 查看设置的命令行参数
gcc main.c -g 得出的 a.out 才能调试
l (list) 列出代码
r (run) 执行
start 单步执行
set follow-fork-mode child 跟踪父进程(默认跟踪父进程)
set follow-fork-mode parent 跟踪子进程
一定要在fork之前调用
exec族函数
调用exec不会产生新的进程,不会产生新的进程id,旧程序被新进程替代 exec 函数族函数只有错误返回值,没有错误则执行完就结束,不会再执行调用函数
execlp 函数 加载一个进程 借助PATH环境变量
execl 函数 加载一个进程 通过路径+程序名来加载
execvp 函数 加载一个进程 使用自定义环境变量env
dup2
完成文件描述符的拷贝
|---|
| 1 |stdin
|---|
| 2 |stdout
|---|
| 3 |stderr
|---|
| 4 |file
|---|
dup2(2,3)
将2的付给3
孤儿进程和僵尸进程
孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,则称init进程领养了子进程
僵尸进程:进程终止,父进程未对其进行回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程,僵尸进程不能使用kill指令进行终止,因为僵尸进程已经结束。
wait() waitpid() 初步
pid_t wait(int *status)
父进程回收子进程终止信息。该函数有三个功能
1、阻塞等待子进程退出 父进程不会做其他事情
2、回收子进程残留资源 内核中不存在PCB
3、获取子进程结束状态 得到子进程如何回收的
wait 成功返回退出进程id 否则返回-1
wait(int *status) status是传出参数 获取子进程退出状态
通过宏获取状态
1、
WIFEXITED(status) 为非0 -> 进程正常结束
WEXITSTATUS(status)如上宏为真,使用此宏 ->获取进程退出状态
2、
WIFSIGNALED(status) 为非0 -> 进程异常终止
WTERMSIG(status)如上宏为真,使用此宏 ->取得使进程终止的那个信号的编号
3、
WIFSTOPPED(status)为非0->进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 ->取得使进程暂停的那个信号的编号
WIFCONTINUED(status)为真->进程暂停后已经继续运行
pid_t waitpid(pid_t pid, int* status, int options) 成功返回清理掉的子进程ID,成功返回-1
特殊参数和返回情况
参数pid:
>0 回收指定进程ID
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
<-1 回收指定进程组的任意子进程
返回 0阻塞
WNOHANG(非阻塞 轮询) ,且子进程正在运行。
一次wait()或waitpid()调用只能清理一个子进程,清理多个子进程应使用循环。
IPC方法 进程间通信
1、管道 使用最简单
2、信号 开销最小
3、共享映射区 无血缘关系
4、本地套接字 最稳定
1、管道:
本质是伪文件
由两个文件描述符引用,一个表示读端,一个表示写端
规定数据从管道的写端流入管道,从读端流出
管道原理:管道实为内核使用环形队列机制,借助内核缓冲区4k
管道的局限性:
1、数据自己读不能自己写
2、数据一旦被读走,便不在管道中存在,不可反复读取
3、由于管道采用半双工通信方式,因此数据只能在一个方向上流动
4、只有在公共祖先的进程间使用管道
pipe 函数
int pipe(int pipefd[2]) 成功返回0 失败返回-1
fd[0] -> r
fd[1] -> w
关闭读端 close(fd[0]);
fifo 有名管道 用于非血缘关系进程间通信
2、共享内存:
创建共享内存的函数mmap函数
mmap函数
void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
addr:简历映射区的首地址,由linux内核指定。使用时,直接传递NULL
length:欲创建映射区的大小
prot:映射区权限 PROT_READ、PROT_WRITE、 PROT_READ|PROT_WRITE
flags:标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
MAP_SHARED:会将映射区所做的操作反映到物理设备(磁盘)上。
MAP_PRIVATE:映射区所做的修改不会反映到物理设备上。
int munmap(void* addr, szie_t length);
fd:用来创建映射区的文件描述符
offset:映射文件的偏移(4k的整数倍)
返回值: 成功返回首地址,失败:MAP_FAILED宏
无法创建大小为0的共享空间
创建的文件必须大小不为0
借助共享内存访问磁盘文件 (之前 open write)
mmap 注意事项:
1、是否可以使用open O_CREATE创建缓冲区
不可以使用open O_CREATE创建缓冲区
因为创建出来的文件大小为0
但是不可以创建大小为0的缓冲区
但是可以创建大小为0的堆空间
2、(映射区首地址)mem++, munmap可否成功?
不能成功因为首地址不一样了
创建的映射区首地址和释放的映射区首地址要一样
3、如果open时O_RDONLY,mmap时PROT参数指定PROT_READ|PROT_WRITE会怎么样?
创建映射区的权限要小于等于打开文件的权限,映射区创建的过程中会产生一次对文件的读操作。
4、如果文件偏移量为1000会怎样?
offset(偏移值)的值必须为4k的整数倍
映射区内核MMU创建的,MMU映射时为4K
5、如果不检测mmap的返回值会怎么样?
会出错
6、mmap什么情况下会调用失败?
参数无效的情况下
7、对mem 越界操作会怎样?
无法munmap
无法释放
8、文件描述符先关闭对mmap映射有没有影响?
文件描述符先关闭没有影响
文件描述符就是操作一个文件的句柄,只要建立映射区成功就可以直接关闭。
父子进程或有血缘关系的进程之间使用共享内存
MAP_SHARED 父子共享
MAP_PRIVATE 进程独享
匿名映射区
不需要将东西写入文件 使用宏 MAP_ANONYMOUS(或MAP_ANOP) 这个宏只可以在linux下使用,其他类unix下不能使用
其他类Unix系统可以使用/dev/zero --- /dev/null
fd = open("/dev/zero",O_RDWR);
mmap 无血缘关系系进程间通信
使用同一文件创建映射区,
指定MAP_SHARED
可多读端多写端