硬件结构决定了软件行为:
程序运行,会被加载到内存中
因为程序要想被执行,就需要放在内存中被cpu进行读取处理。
例如:
qq聊天实现:键盘采集输入,放到内存,cpu进行处理,网卡发送数据
操作系统:linux
组成:内核+应用
定位:计算机上搞管理的软件,管理计算机上的软硬件资源
管理:
用户 库函数 系统调用接口 操作系统 内核 硬件驱动 键盘,鼠标....硬件
系统调用接口:操作系统提供的用户访问系统内核的接口
库函数与系统调用接口的关系:库函数实际上就是对系统调用接口进行的一层封装后的接口
进程:
进程概念
进程:运行中的程序
pcb:进程控制块:程序运行的描述
通过程序的运行描述,操作系统就可以调度哪个程序可以占用cpu去运行指令,要运行哪个程序,则操作系统找到对应程序的pcb,在pcb取出程序的运行所需信息,加载到cpu上,cpu就开始运行这个程序了。
对于操作系统来说,进程就是一个程序运行的描述,通过这个描述,操作系统可以进行程序的调度运行管理。
实际上对于系统来说,进程就是pcb
这个程序运行的动态描述,叫做pcb进程控制块,在Linux下是一个task_struct结构体。
描述信息:内存指针,上下文数据,程序计数器,进程ID-pid,IO信息,进程优先级,进程状态,记账信息…
时间片:cpu调度运行程序的时间段
时间片过后,就该切换调度下一个进程了
并发:一种轮询处理的方式
并行:同时运行
进程操作
进程的简单操作:
创建进程:进程就是一个pcb是一个task_struct结构体(linux中),创建一个进程实际上就是创建了一个task_struct结构体
pid_t fork(void) --创建进程的接口–通过复制调用这个接口的进程(父进程),创建一个新的进程(子进程)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
//子进程睡眠5秒退出,父进程继续进行,陷入死循环
int mian(int argc, char *argv[])
{
printf("-------");
pid_t pi = fork();
//子进程复制了父进程,因此往后的代码父进程都会运行,但是因为返回值不同因此进入不同的if
if(pid < 0)
{
//出错了
}
else if(pid == 0)
{
//子进程--对于子进程返回值是0
sleep(5);
exit(0); //退出进程--谁调用就退出谁
}
else
{
//父进程--对于父进程返回的是子进程的pid,因此返回值是大于0
}
printf("hello:%d\n", getpid());
while(1)
sleep(1);
return 0;
}
ps -ef :查看所有进程信息
ps -ef | grep fork :查找指定字符(fork)的进程信息
ps -aux | grep fork:更详细信息
进程状态
进程状态:用于操作系统对于进程的管理(什么状态对进程进行什么样的操作)
运行态,就绪态,阻塞态
linux中的进程状态:
运行态(R):正在运行或者轮转到时间片能够运行统称运行态
可中断休眠态S:可以被中断的休眠状态(满足唤醒条件,或者休眠被中断则进入运行态)
不可中断休眠态D:不能被中断的休眠状态(满足唤醒条件之后才会进入运行态)
停止态T:程序停止运行的状态(依然会被调度,但是什么都不做)
死亡态X:
僵尸态Z:进程已经退出了不再调度了,但是这个进程的资源还没有被完全释放,等待处理的一种状态。
僵尸进程:处于僵尸态的进程,是一种退出,但是资源没有完全被释放的进程
产生:紫禁城先于父进程退出,但是父进程没有关注到子进程的退出,因此资源不会完全释放子进程的资源,这个子进程进入僵尸状态。
子进程退出之后,在进程pcb中保存了自己的退出返回值,在父进程没有关注处理的情况下,pcb资源是不会被释放
危害:资源泄露(一种是pcb所占的内存资源一直无法被回收,一种是一个用户所能创建的进程数量是有限制的)
解决方案:
处理:退出父进程
避免:进程等待
孤儿进程:父进程先于子进程退出,子进程就会成为孤儿进程,运行在后台,父进程称为1号进程(早期名字叫init进程,后期叫systemd)
精灵进程(守护教程):
环境变量
环境变量:保存运行环境参数的变量;使程序运行环境配置更加灵活;以及可以通过环境变量实现进程间的小量通信(父子进程的传递)
命令操作:env(查看环境变量),set(查看所有变量),echo(查看指定变量),export(声明或设置环境变量),unset(删除变量)
典型环境变量:
PATH:程序运行的默认搜索路径—在命令行终端中输入命令名称可以直接执行对应名称的命令程序,实际上是因为shell扑捉到输入的命令名称之后,然后去PATH环境变量指定的路径下去找这个程序,找到了就运行,找不到就报命令没有找到的错误。
环境变量接口:
char *getenv(const char *name);—通过名称获取值;
铺垫:shell中运行的程序,父进程都是shell;或者反过来说,shell中运行的进程都是shell的子进程
代码操作:getenv()、**extern char environ; 、main(int argc, char *argv[], char *env[])
程序地址空间
程序地址空间:
地址:通常所说的地址,都是内存的地址,是内存单元的编号
实际上在进程中,程序访问的这些地址(变量地址…)实际上是一个假地址
将其称之为–虚拟地址;
我们所说的程序地址空间实际上叫做进程的虚拟地址空间
虚拟地址空间实际上是系统给进程所描述的一个假的地址空间,是一个mm_struct结构体
系统会为每一个进程都描述一个假的地址空间,进程访问的都是虚拟地址,访问内存数据的时候,先将虚拟地址转换成物理地址然后访问
系统为每个进程都描述一个完整的,连续的,线性的虚拟地址空间,实际物理内存用的时候再给进程分配。这样的话,对于每个进程自己的感觉,看起来都有一块完整的,连续的内存可以使用。
虚拟地址空间,是系统为每个进程通过mm_struct结构体虚拟的一个地址空间,使用虚拟地址空间的目的是为了让进程能够访问一块连续的,完整的地址,并且经过页表映射到物理内存之后,可以实现数据在物理内存上的离散式存储,提高内存利用率,并且在页表中可以进行内存访问控制
虚拟地址如何通过页表获取物理地址的呢?
内存管理方式
- 分段式内存管理:将地址空间分为多段(代码段,数据段…)便于编译器进行地址管理
分段式虚拟地址组成:段号+段内的地址偏移量;在系统中有一个段表:一个个段表项(段号,物理内存段起始地址)
- 分页式内存管理:将地址空间分为多个小块(页),提高内存利用率
分页式虚拟地址组成:页号+页内偏移;在系统中有一个页表(页号,物理内存快起始地址,权限控制,缺页中断位…)
4G(2^32 )内存;内存页大小4k(1k是1024,为2^10 ; 4k为2^12)
所以内存大小除以内存页大小,一共有2^20个内存页
因此32位内存地址,搞20位就是页号,低12位就是页内偏移
- 段页式内存管理:将虚拟地址进行分段,在每个分段内进行分页式管理,集合了分段分页的优点进行内存管理
进程控制:
创建
pid_t fork();
– 通过赋值父进程创建一个子进程
–父子进程代码共享,数据独有
返回值:错误返回-1,对于父进程 返回值大于0 - 子进程pid,对于子进程返回0
写时拷贝技术:创建子进程后,子进程与父进程各自有自己的虚拟地址空间,但是数据映射的实际上是同一块物理内存,等待内存发生改变的时候为子进程重新开辟一块独立的空间,保存子进程的数据(保持进程独立性)
对比:
pid_vfork(void);(已经很少使用)
通过赋值父进程创建一个子进程,父子进程公用虚拟地址空间
创建子进程后,父进程会阻塞直到子进程exit退出或者(程序替换)之后才会运行。
公用同一个虚拟地址空间,同时运行会造成栈混乱
退出
进程退出:退出一个进程
main函数中调用return;
使用库函数void exit(int status);
在任意位置调用都可以退出进程,退出之前刷新文件缓冲区
使用系统调用接口 void _exit(int status);
在任意位置调用都可以退出进程
进程的退出返回值:
正常退出:通过三种退出方式退出的进程(结果是否符合预期)
异常退出:程序因为某些错误异常崩溃退出
进程的返回值,实际上只是使用了一个字节进行保存
查看上一次系统调用接口使用错误原因的接口:
void perror(consst char *s);
等待
父进程等待子进程退出;获取退出子进程的返回值,释放资源避免产生僵尸进程
wait/waitpid
阻塞:为了完成一个功能,发起一个调用,但是不具备完成条件,一直等待。
非阻塞:为了完成一个功能,发起一个调用,但是不具备完成条件,立即返回金长城返回值的获取。(通常要求循环操作)
替换
替换进程正在调度运行的程序;
加载一个新的程序到内存中,更新当前进程的页表映射信息到新的程序上。
希望创建的子进程能够完成一个新的任务,多进程更加稳定。
execl/execlp/execle/execvp/execve
l和v的区别:参数赋予方式不同;
有没有p的区别:新的此程序是否需要带路径;
有没有e的区别:是否自己设置环境变量;
从零开始编写一个minishell:
shell:命令行解释器
1.捕捉键盘输入;(scanf,fgets,gets)
2.解析输入信息-命令名称,运行参数
3.创建子进程,(fork)让子进程 执行对应命令名称的程序(程序替换)(execvp)
子进程替换失败必须退出,成功运行完命令程序会自己退出
4.等待子进程退出(wait)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
whlie(1)
{
printf("[user@host path]$ ");
//刷新缓冲区
fflush(stdout);
char buf[1024] = {0};
fgets(buf, 1023, stdin);
buf[strlen(buf) - 1] = '\0';
int myargc = 0;
char *ptr = buf, *myargv[32] = {NULL};
whlie(*ptr != '\0')
{
if(*ptr != ' ')
{
myargv[myargc] = ptr;
myargc++;
while(*ptr != '\0' && *ptr != ' ')
ptr++;
*ptr = '\0';
}
ptr++;
}
myargv[myargc] = NULL;
//测试打印
fpr(int i = 0; i < myargc; i++)
{
printf("[%s]\n", myargv[i]);
}
//创建进程
pid_t pid = fork();
//如果小于0,创建进程失败
if(pid < 0)
{
perror("fork error");
continue;
}
else if(pid == 0)
{
//子进程进来之后进行程序替换
execvp(myargv[0], myargv);
//如果运行失败,进行退出
perror("execvp error");
exit(-1);
}
wait(NULL);
}
return 0;
}