主题:
日常学习记录系列。包括:
- 消息队列通信(接收部分)以及消息队列的管理
- 缺页中断步骤和与一般中断的不同之处
- fork函数和exec函数配合使用。exit和wait函数的配合使用
- 页表寻址机制(二级三级四级)
- 缺页置换算法
- 线程之间通信方式
代码:
#include <stdio.h>
#include <iostream>
#include <sys/types.h>
#include <unistd.h> //fork函数
#include <sys/ipc.h>
#include <cstring>
#include <sys/msg.h> //里买你有消息的发送函数
#include <unistd.h>
#include <sys/wait.h> //wait函数
using namespace std;
class Message //消息的类别,必须有一个type
{
public:
Message(long t, char* s)
{
type = t;
strcpy(message, s);
}
Message()
{
}
long type;
char message[100];
int flag;
};
int main()
{
//--------------消息队列通信 接受-------------------------
//读取消息的时候,是先进先出的方式。最先进去的消息,也最先被读出
/*
key_t key = ftok("/home/xuyangli/", 'x');
if(key<0)
{
perror("key");
return 0;
}
cout<<"key已经准备好 "<<key<<endl;
int msgid = msgget(key, IPC_CREAT|0666);
if(msgid<0)
{
perror("getid");
return 0;
}
cout<<"id为: "<<msgid<<endl;
Message messrcv;
memset(&messrcv, 0, sizeof(messrcv)); //接受的结构体无需type要求
//但是需要i加上你需要哪个类型的数据,也就是第四个参数,为0代表
//第一个即可。其他的则只会筛选符合要球的。
//取出一个消息,这个消息就没有了。从链表里面摘除。
int rv = msgrcv(msgid, &messrcv, sizeof(messrcv), 1, IPC_NOWAIT );
if(rv<0)
{
perror("receive");
return 0;
}
cout<<messrcv.message<<endl;
cout<<messrcv.type<<endl;
cout<<messrcv.flag<<endl;
//管理消息队列,IPC_STAT表示将信息放在结构提里面。IPC_RMID表示删除这个消息链表
msqid_ds control;
if(msgctl(msgid, IPC_STAT, &control)<0)
{
perror("control");
return 0;
}
cout<<control.msg_qnum<<" "<<control.msg_lspid<<endl;
*/
//--------------------缺页中断-----------------------
//缺页中断类似一般中断,需要经过1 保护cpu现场 2 分析中断原因 3 转入缺页中断处理程序
//4 回复cpu现场
//但是特殊之处在于缺页中断是发生在指令执行期间,而一条指令执行期间也可能产生多次中断
//缺页中断返回的是执行下一条指令,而缺页中断返回的是执行中断的指令。
//------------------fork函数的使用---------------------------
//fork可以创建一个和调用进程一样的子进程,但是最为常见的用法不是调用一个一样的子进程,
//而是紧跟一个exec()系统调用,来让子进程执行另外的一个二进制映像。为了不必要的开销,
//现代linux使用 写时复制 的方法。写时复制是一种惰性优化的策略。基本思想是创建子进程
//的时候,子进程和父进程共享父进程的原始页(数据段和代码段),对于子进程来说,好像是自己也有一份。仅仅
//当子进程需要进行对页面的写入的时候,才会进行复制。创建一个副本。
//这样,那些紧跟fork之后就有exec的程序,不必做无用的复制。
// exec是一组函数,由于c语言中没有函数重载,因此使用一定的命名参数来对应参数的不一样
//exec的函数的参数格式为 1 可执行文件路径 2 执行参数 3 环境变量(可要可不要)
//v和l用来修饰执行参数的使用方式。当有v时,使用一个char* []来表示各参数,最后一个必须是
//NULL,l表示使用类似列表的方式来表示各个参数,也就是在参数列表里面写,用,隔开各参数。
//p修饰可执行文件路径,当有p是表示不必写绝对路径,它会自己找。
//e表示有环境变量。否则表示没有。
/*
pid_t pid = fork();
if(pid==0)
{
cout<<"开始执行子程序\n";
char* arg[1] = {NULL};
execvp("/home/xuyangli/code/20210430of1",NULL); //最后一定有一个NULL,即使没有命令参数,也要家NULL
exit(0);
}
else if(pid>0)
{
wait(NULL); //wait.h中的函数
cout<<"父程序\n"; //后运行,说明其了作用
return 0;
}
else{
return 0;
}
*/
//------------------return和exit exec wait函数-----------------
//return是语言级的一个关键字,exit是操作提同提供的一个函数
//return 表示函数退出,exit表示进程退出。
//exit(0)表示程序正常退出 exit(1)表示非正常退出
// wait函数表示等待 子进程 退出。会将父进程挂起。通常是子进程执行exit之后,wait会推出
//-----------------页表寻址----------------------------------
//每一个页表项都存储这个页的 基地址。一个逻辑地址的高位部分会通过 硬件 来查找页表,完成
//物理的基地址的查找,再结合 低位 的偏移量来获得真正的物理地址。
//两级页表机制,分为 全局页目录表索引(PGD),页表入口索引(PTE)。CR3寄存器中存有
//PGD的基地址,根据高十位的偏移,可以找到可以描述这个地址的PGD,PGD存放的是PTE的基地址
//再根据中间的10位的偏移量。然后再查找得到页表基地址。两级页表机制是最基本的页表机制
//,因为页表的存放一般是仅仅连续存放于一个页表之内。
//规律:虚拟地址有多少位,决定了最终从几个页表项中间选择来寻找到 想要找到的页表。
//虚拟地址的位数决定了虚拟空间有多大。
//物理地址有多少多少位,决定了一个页表项有多大。物理地址的位数和虚拟地址的位数不一定一致。
//在x86引入物理地址扩展,物理地址变成了36位之后,由于虚拟地址仍然是32位,但是一个页
//里面可以存储的页表项从1024(4K/4B),转变为512(4K/8B)。之所以之前是4B那是因为
//之前的物理地址是4B,一个基地址和最终的地址一样,都是一个物理地址。
//经过迭代发展,还出现了三级,四级寻址,支持的 虚地址 的位数也变成了48位
//64位cpu和48位虚拟空间之后出现并且成为主流。支持四级页表机制。
//--------------------进程的缺点,引入线程的原因---------------------
//进程缺点:进程在同一时间之可以干一件事。如果进程阻塞,整个进程就会挂起,即使有些工作
//不以来与等待的资源,仍然不会执行。
//引入线程:可以 节省资源(调度资源和通信资源),线程可以使多CPU系统更加高效,可以同时
//执行多个线程。
//线程的执行也是需要加线程锁的。
//------------------------线程之间的同步方式--------------------
//线程之间的同步主要以来信号量,因为主要通过共享内存的方式来进行通信。
//信号量以来于操作系统的PV操作:P:当信号量>0,那么就直接-1,如果信号量<=0,那么就阻塞
//然后-1.V:当信号量<0,那么就+1,然后唤醒一个阻塞的进程。如果>=0,那么直接+1
//系统调用有sem_wait[p] sem_post[v]。
//信号量有互斥量(也叫互斥锁),有条件信号量(条件锁)。
//
//-----------------缺页置换算法----------------------------
//当缺页的时候,如果可以分配的内存已经满了,那么就会找一个页来进行置换。置换算法常用的
//有先进先出(FIFO),最近最少使用(LRU)。
return 0;
}