Linux系统编程阶段:使用Linux系统提供的接口完成指定功能程序的编写
进程概念:冯诺依曼体系结构,操作系统,进程概念,进程状态,环境变量,程序地址空间
-
冯诺依曼体系:现代计算机的硬件体系结构(规定了现代计算机应该具有哪些硬件单元)
硬件单元:输入设备-键盘,输出设备-显示器,存储器-内存条(内存-内存条,外存-硬盘),运算器,控制器(运算器+控制器=中央处理器cpu;主频:2.5GHz–每秒钟cpu能够处理多少指令)
所有设备都是围绕存储器工作的,存储器是作为中间的数据缓冲而存在的
硬件结构决定了软件行为,程序运行,会被加载到内存中 -
操作系统:Linux
组成:内核+应用
定位:计算机上搞管理的软件,管理计算机上的软硬件资源系统调用接口:操作系统提供的用户访问系统内核的接口
库函数与系统调用接口的关系:库函数实际上就是对系统调用接口进行的一层封装后的接口 -
进程概念:进程就是运行中的程序
pcb(进程控制块):程序运行的描述
通过程序的运行描述,操作系统就可以调度哪个程序可以占用cpu去运行指令(cpu的分时机制:多任务操作系统中使用分时机制实现多任务并发处理,时间片:cpu调度运行程序的时间段,时间片过后,就该切换调度下一个进程了)。要运行哪个程序,则操作系统找到对应程序的pcb,在pcb上取得程序的运行所需信息,加载到cpu上,cpu就开始运行这个程序了。一个程序可以作为多个进程的运行程序。
对于操作系统来说,进程就是一个程序运行的动态描述(即pcb进程控制块,在linux下是一个task_struct结构体),通过这个描述,操作系统可以进行程序的调度运行管理。即实际上对于操作系统来说,进程就是pcb。
描述信息:内存指针,上下文数据,程序计数器,进程ID-pid,IO信息,进程优先级,进程状态,记账信息并发:一种轮询处理的方式
并行:同时运行进程的简单操作:
创建进程:创建进程实际上就是创建了一个task_struct结构体
创建进程的接口:pid_t fork(void)—通过复制调用这个接口的进程(父进程),创建一个新的进程(子进程)。
将父进程整体复制,但是因为程序计数器保存的位置是创建子进程成功之后的指令,因此复制过来之后,子进程从这里开始运行。
返回值:对于父进程会返回创建的子进程的pid,对于子进程会返回0getpid():返回调用进程的进程ID(PID)
getppid():返回调用进程的父进程的进程ID。
ps -ef / -aux:查看所有进程的信息进程创建代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
//出错了
}
else if(pid == 0)
{
printf("子进程id=%d\n",getpid());
}
else
{
printf("父进程id=%d\n",getpid());
}
while(1) sleep(1);
return 0;
}
运行结果
通过ps -ef查看的结果
-
进程状态:用于操作系统对于进程的管理(什么状态该对进程进行什么样的操作)
运行态,就绪态,阻塞态
Linux中的进程状态:
状态后边的+,表示这个进程是一个前台进程(当前占据终端的进程)
运行态(R):正在运行或者轮转到时间片能够运行统称为运行态
可中断休眠态(S):可以被中断的休眠状态(满足唤醒条件,或者休眠被中断则进入运行态)
不可中断休眠态(D):不能被中断的休眠状态(满足唤醒条件之后才会进入运行态)
停止态(T):程序停止运行的状态(依然会被调度,但是什么都不做)
死亡态(X):这个状态只是一个返回状态,不会在任务列表里看到这个状态
僵尸态(Z):进程已经退出不再调度了,但这个进程的资源还没有被完全释放,等待处理的一种状态僵尸进程:处于僵尸态的进程,即是一种退出了但资源还没有完全被释放的进程。
产生的原因:子进程先于父进程退出,但是父进程没有关注到子进程的退出,因此系统不会完全释放子进程的资源,这个子进程进入僵尸状态。子进程退出后,在进程pcb中保存了自己的退出返回值,在父进程没有关注处理的情况下,pcb资源是不会被释放的。
危害:资源泄漏(一种是pcb所占的内存资源一直无法被回收,另一种是一个用户所能创建的进程数量是有限制的—可通过ulimit -a查看)
解决方案:
处理:退出父进程
避免:进程等待创建僵尸进程:
int main()
{
pid_t pid = fork();
if(pid < 0)
{
//子进程复制了父进程,因此往后的代码父子进程都会运行,
//但是因为返回值的不同而进入不同的if
//出错了
}
else if(pid == 0)
{
//子进程---对于子进程返回值是0
printf("子进程id=%d\n",getpid());
sleep(5);
exit(0);//退出进程---谁调用就退出谁
}
else
{
//父进程---对于父进程返回的是子进程的pid,因此返回值是大于0的
printf("父进程id=%d\n",getpid());
}
while(1) sleep(1);
return 0;
通过ps -aux查看僵尸子进程:
孤儿进程:父进程先于子进程退出,子进程就会成为孤儿进程,运行在后台,父进程成为1号进程(早期名字叫init进程,后期叫systemd)
创建孤儿进程:
int main()
{
pid_t pid = fork();
if(pid < 0)
{
//子进程复制了父进程,因此往后的代码父子进程都会运行,但是因为返回值的不同而进入不同的if
//出错了
}
else if(pid == 0)
{
//子进程---对于子进程返回值是0
printf("子进程id=%d\n",getpid());
}
else
{
//父进程---对于父进程返回的是子进程的pid,因此返回值是大于0的
printf("父进程id=%d\n",getpid());
sleep(5);
exit(0);//退出进程---谁调用就退出谁
}
while(1) sleep(1);
return 0;
}
通过ps -ef查看孤儿进程以及其父进程
五秒后父进程退出,孤儿进程的父进程变为1号进程
守护进程(精灵进程):特殊的孤儿进程,运行在后台,脱离与终端的关系,脱离登录会话
-
环境变量:也是一种变量,是一种保存系统运行环境参数的变量;便于运行环境参数的配置,进程之间的数据通信(子进程的环境变量就是从父进程而来的)
命令操作:
env—查看当前运行环境中的所有环境变量
set—查看当前运行环境中的所有变量
export—声明或设置一个环境变量
echo—查看指定变量(也可通过echo $PATH查看环境变量)
unset—删除变量PATH:程序运行的默认搜索路径—在命令行终端中输入命令名称可以直接执行对应名称的命令程序,实际上是因为shell捕捉到输入的命令名称之后,会去PATH环境变量指定的路径下去找这个程序,找到了就运行,找不到就报命令没有找到的错误。
环境变量接口:
char* getenv(const char* name)—通过名称获取值;
int setenv(const char* name, const char* value, int overwrite) / int putenv(char* string)—设置环境变量代码操作:
getenv()、extern char** environ、main(int argc, char* argv[], char* env[])
main函数有三个参数—程序运行参数的个数,程序运行参数字符串地址,环境变量的字符串地址shell中运行的程序,父进程都是shell,shell中运行的进程都是shell的子进程,它们的环境变量都是通过shell设置的,每个shell终端都是一个运行环境,不同的终端拥有不同的shell,每个shell都拥有它自己设置的环境变量,因此在不同的终端中运行环境可能不同。
-
程序地址空间:
地址:通常所说的地址,都是内存的地址,是内存单元的编号
而在进程中,程序访问的这些地址(变量地址…)实际上是一个假地址—虚拟地址,我们所说的程序地址空间实际上叫做进程的虚拟地址空间。虚拟地址空间实际上是系统给进程所描述的一个假的地址空间,是一个mm_struct结构体。并且每个进程都有自己独立的虚拟地址空间,也就是有自己的mm_struct内存描述。进程访问的都是虚拟地址,访问内存数据的时候,先将虚拟地址转换为物理地址然后访问。系统为每个进程都描述一个完整的,连续的,线性的虚拟地址空间,实际物理内存用的时候再给进程分配。这样的话,对于每个进程来说,看起来都有一块完整的,连续的内存可以使用。
给每个进程虚拟一个地址空间,让进程访问完整连续的地址,但是这些虚拟地址在使用的时候通过页表映射一块物理内存地址,进行访问物理内存。通过映射这种方式,实现了数据在物理内存上的离散式存储,提高内存利用率。虚拟内存空间:是系统为每个进程通过mm_struct结构体虚拟的一个地址空间,使用虚拟地址空间的目的是为了让进程能够访问一块连续的,完整的地址。并且经过页表映射到物理内存之后,可以实现数据在物理内存上的离散式存储,提高内存利用率,并且在页表中可以进行内存访问控制。
虚拟地址是如何通过页表获取到物理地址的呢?
内存管理方式:
分段式内存管理:将地址空间分为多段(代码段、数据段…),便于编译器进行地址管理。分段式虚拟地址组成:段号+段内的地址偏移量;在系统中有一个段表:一个个段表项(段号+物理内存起始地址)分页式内存管理:将地址空间分为多个小块(页),实现数据的离散式存储,提高内存利用率,提高内存安全访问控制。分页式虚拟地址组成:页号+页内偏移;在系统中有一个页表(页号,物理内存块起始地址,权限控制,缺页中断位…)
若4G内存(232),内存页大小为4K(212),则有220个内存页,因此在32位内存地址下,高20位就是页号,低12位就是页内偏移。段页式内存管理:将虚拟地址空间进行分段,在每个分段内进行分页式管理,集合了分段分页的优点进行内存管理。
缺页中断:当我们通过虚拟地址访问物理内存的时候,发现数据没有在物理内存中。
原因:内存置换(当物理内存不够用的时候,将物理内存中不活跃的数据,交换到磁盘的交换分区上,腾出内存用于新的数据处理)
内存置换算法:LRU—最久未使用