01 低级
1.1 添加简单程序
app.c
#include "types.h"
#include "stat.h"
#include "user.h"
int main(int argc, char *argv[]) {
printf(1, "Hello World\n");
exit();
}
UPROGS 中添加
make 后生成
运行结果
1.2 系统调用示例
查看pid
1.3 系统调用getcpuid()
- 编写 pcpuid.c 代码
- 同样修改 Makefile,添加一行 pcpuid 即可, 系统启动以后直接输入 pcpuid 打印cpu号
- 在 syscall.h 中添加一行系统调用号 22 号
- 然后再增加用户态的进入接口,在 user.h 中添加一行 int getcpuid(void); (getcpuid函数在 pcpuid.c中调用了)
- 在 usys.S 中加入一行 SYSCALL(getcpuid) 以定义用户态的入口。将系统调用号保存在eax寄存器中 触发软中断 int 0x40(int 64)进入内核的中断处理函数
=====================================================================================================================================================================内核与应用层分割线
- 在 syscall.c 中的分发函数表中添加 getcpuid 函数所对应的表项 [SYS_getcpuid] sys_getcpuid (以eax及系统调用号 在 syscalls[]系统调用表中找到需要执行的代码)
- 然后修改 syscall.c,添加一行 sys_getcpuid 函数的声明 extern int sys_getcpuid(void);
- 然后开始实现 sys_getcpuid 函数。在 sysproc.c 中添加 sys_getcpuid 的实现
sys_getcpuid(void) {
return getcpuid();
}
- 在 proc.c 中实现内核态的 getcpuid 函数
int getcpuid() {
return cpunum();
}
- 最后在defs.h中加入 getcpuid 用作内核态代码调用 getcpuid()时的函数原型 (为了让sysproc.c中的sys_getcpuid()可以调用proc.c中的getcpuid())
运行结果
1.4 观察调度过程
#include "types.h"
#include "stat.h"
#include "user.h"
int main(int argc, char *argv[]) {
printf(1, "Hello World\n");
int a;
a = fork();
a = fork();
while(1) {
a++;
}
exit();
}
实验结果:
可以看出每次在不同的进程运行,说明时间片在轮转
02 中级
2.1 调整时间片长度
运行结果
cprintf(“slice left:%d ticks,%d %s %s”, p->slot, p->pid, state, p->name);
输出4个参数 时间片剩余数 进程号 进程状态 进程名字
2.2 信号量
spinlock.c
int sys_sem_create(void) {
int n_sem, i;
if(argint(0, &n_sem) < 0 )
return -1;
for(i = 0; i < SEM_MAX_NUM; i++) {
acquire(&sems[i].lock);
if(sems[i].allocated == 0) {
sems[i].allocated = 1;
sems[i].resource_count = n_sem;
cprintf("create %d sem\n",i);
release(&sems[i].lock);
return i;
}
release(&sems[i].lock);
}
return -1;
}
int sys_sem_free(){
int id;
if(argint(0,&id)<0)
return -1;
acquire(&sems[id].lock);
if(sems[id].allocated == 1 && sems[id].resource_count > 0){
sems[id].allocated = 0;
cprintf("free %d sem\n", id);
}
release(&sems[id].lock);
return 0;
}
int sys_sem_p()
{ int id;
if(argint(0, &id) < 0)
return -1;
acquire(&sems[id].lock);
sems[id]. resource_count--;
if(sems[id].resource_count<0) //首次进入、或被唤醒时,资源不足
sleep(&sems[id],&sems[id].lock); //睡眠(会释放sems[id].lock才阻塞)
release(&sems[id].lock); //解锁(唤醒到此处时,重新持有sems[id].lock)
return 0; //此时获得信号量资源
}
int sys_sem_v()
{ int id;
if(argint(0,&id)<0)
return -1;
acquire(&sems[id].lock);
sems[id]. resource_count+=1; //增1
if(sems[id].resource_count<1) //有阻塞等待该资源的进程
wakeup1p(&sems[id]); //唤醒等待该资源的1个进程
release(&sems[id].lock); //释放锁
return 0;
}
sh_rw_lock.c文件
#include "types.h"
#include "stat.h"
#include "user.h"
int main(){
int id=sem_create(1);
int pid = fork();
int i;
for(i=0;i<100000;i++){
sem_p(id);
sh_var_write(sh_var_read()+1);
sem_v(id);
}
if(pid >0){
wait();
sem_free(id);
}
printf(1,"sum=%d\n",sh_var_read());
exit();
}
运行结果
2.3 进程间通信
2.3.1 共享内存
sharemem.c
#include "types.h"
#include "defs.h"
#include "param.h"
#include "mmu.h"
#include "proc.h"
#include "spinlock.h"
#include "memlayout.h"
#define MAX_SHM_PGNUM (4) //每个共享内存最带4页内存
struct sharemem
{
int refcount; //当前共享内存引用数,当引用数为0时才会真正回收
int pagenum; //占用的页数(0-4)
void* physaddr[MAX_SHM_PGNUM]; //对应每页的物理地址
};
struct spinlock shmlock; //用于互斥访问的锁
struct sharemem shmtab[8]; //整个系统最多8个共享内存
void
sharememinit()
{
initlock(&shmlock,"shmlock"); //初始化锁
for (int i = 0; i < 8; i++) //初始化shmtab
{
shmtab[i].refcount = 0;
}
cprintf("shm init finished.\n");
}
int
shmkeyused(uint key, uint mask)
{
if(key<0 || 8<=key){
return 0;
}
return (mask >> key) & 0x1; //这里判断对应的系统共享内存区是否已经启用
}
int
shmrm(int key)
{
if(key<0||8<=key){
return -1;
}
//cprintf("shmrm: key is %d\n",key);
struct sharemem* shmem = &shmtab[key];
for(int i=0;i<shmem->pagenum;i++){
kfree((char*)P2V(shmem->physaddr[i])); //逐个页帧回收
}
shmem->refcount = 0;
return 0;
}
int
shmadd(uint key, uint pagenum, void* physaddr[MAX_SHM_PGNUM])
{
if(key<0 || 8<=key || pagenum<0 || MAX_SHM_PGNUM < pagenum){
return -1;
}
shmtab[key].refcount = 1;
shmtab[key].pagenum = pagenum;
for(int i = 0;i<pagenum;++i){
shmtab[key].physaddr[i] = physaddr[i];
}
return 0;
}
int
mapshm(pde_t *pgdir, uint oldshm, uint newshm, uint sz, void **physaddr)
{
uint a;
if(oldshm & 0xFFF || newshm & 0xFFF || oldshm > KERNBASE || newshm < sz)
return 0; //验证参数
a=newshm;
for (int i = 0;a<oldshm;a+=PGSIZE, i++) //逐页映射
{
mappages(pgdir,(char*)a,PGSIZE,(uint)physaddr[i],PTE_W|PTE_U);
}
return newshm;
}
int
deallocshm(pde_t *pgdir, uint oldshm, uint newshm)
{
pte_t *pte;
uint a, pa;
if(newshm <= oldshm)
return oldshm;
a = (uint)PGROUNDDOWN(newshm - PGSIZE);
for (; oldshm <= a; a-=PGSIZE)
{
pte = walkpgdir(pgdir,(char*)a,0);
if(pte && (*pte & PTE_P)!=0){
pa = PTE_ADDR(*pte);
if(pa == 0){
panic("kfree");
}
*pte = 0;
}
}
return newshm;
}
// 这个方法和allcouvm实现基本一样
int
allocshm(pde_t *pgdir, uint oldshm, uint newshm, uint sz,void *phyaddr[MAX_SHM_PGNUM])
{
char *mem;
uint a;
if(oldshm & 0xFFF || newshm & 0xFFF || oldshm > KERNBASE || newshm < sz)
return 0;
a = newshm;
for (int i = 0; a < oldshm; a+=PGSIZE, i++)
{
mem = kalloc(); //分配物理页帧
if(mem == 0){
// cprintf("allocshm out of memory\n");
deallocshm(pgdir,newshm,oldshm);
return 0;
}
memset(mem,0,PGSIZE);
mappages(pgdir,(char*)a,PGSIZE,(uint)V2P(mem),PTE_W|PTE_U); //页表映射
phyaddr[i] = (void *)V2P(mem);
// cprintf("allocshm : %x\n",a);
}
return newshm;
}
void*
shmgetat(uint key, uint num)
{
pde_t *pgdir;
void *phyaddr[MAX_SHM_PGNUM];
uint shm =0;
if(key<0||8<=key||num<0||MAX_SHM_PGNUM<num) //校验参数
return (void*)-1;
acquire(&shmlock);
pgdir = proc->pgdir;
shm = proc->shm;
// 情况1.如果当前进程已经映射了该key的共享内存,直接返回地址
if(proc->shmkeymask>>key & 1){
release(&shmlock);
return proc->shmva[key];
}
// 情况2.如果系统还未创建此key对应的共享内存,则分配内存并映射
if(shmtab[key].refcount == 0){
shm = allocshm(pgdir, shm, shm - num * PGSIZE, proc->sz, phyaddr);
//新增的allocshm()分配内存并映射,其原理和allcouvm()相同
if(shm == 0){
release(&shmlock);
return (void*)-1;
}
proc->shmva[key] = (void*)shm;
shmadd(key, num, phyaddr); //将新内存区信息填入shmtab[8]数组
}else {
//情况3.如果未持有且已经系统中分配此key对应的共享内存,则直接映射
for(int i = 0;i<num;i++)
{
phyaddr[i] = shmtab[key].physaddr[i];
}
num = shmtab[key].pagenum;
//mapshm方法新建映射
if((shm = mapshm(pgdir,shm,shm-num*PGSIZE,proc->sz,phyaddr))==0){
release(&shmlock);
return (void*)-1;
}
proc->shmva[key] = (void*)shm;
shmtab[key].refcount++; //引用计数+1
}
proc->shm = shm;
proc->shmkeymask |= 1<<key;
release(&shmlock);
return (void*)shm;
}
void
shmaddcount(uint mask)
{
acquire(&shmlock);
for (int key = 0; key < 8; key++)
{
if(shmkeyused(key,mask)){ //对目前进程所有引用的共享内存的引用数加1
shmtab[key].refcount++;
}
}
release(&shmlock);
}
int
shmrelease(pde_t *pgdir, uint shm, uint keymask)
{
//cprintf("shmrelease: shm is %x, keymask is %x.\n",shm, keymask);
acquire(&shmlock);
deallocshm(pgdir,shm,KERNBASE); //释放用户空间的内存
for (int k = 0; k < 8; k++)
{
if(shmkeyused(k,keymask)){
shmtab[k].refcount--; //引用数目减1
if(shmtab[k].refcount==0){ //若为0 ,即可以回收物理内存
shmrm(k);
}
}
}
release(&shmlock);
return 0;
}
int
shmrefcount(uint key)
{
acquire(&shmlock);
int count;
count = (key<0||8<=key)? -1:shmtab[key].refcount;
release(&shmlock);
return count;
}
test.c
#include "types.h"
#include "stat.h"
#include "user.h"
#include "fs.h"
int main(void)
{
char *shm;
int pid = fork();
if(pid == 0){
sleep(1);
shm = (char*)shmgetat(1,3);//key为1,大小为3页的共享内存
printf(1,"child process pid:%d shm is %s refcount of 1 is:%d\n", getpid(), shm, shmrefcount(1));
strcpy(shm, "hello_world!");
printf(1, "child process pid:%d write %s into the shm\n", getpid(), shm);
} else if (pid > 0) {
shm = (char*)shmgetat(1,3);
printf(1,"parent process pid:%d before wait() shm is %s refcount of 1 is:%d\n", getpid(), shm, shmrefcount(1));
strcpy(shm, "share_memory!");
printf(1,"parent process pid:%d write %s into the shm\n", getpid(), shm);
wait();
printf(1,"parent process pid:%d after wait() shm is %s refcount of 1 is:%d\n", getpid(), shm, shmrefcount(1));
}
exit();
}
2.3.2 消息队列
messagequeue.c
#include "types.h"
#include "defs.h"
#include "param.h"
#include "mmu.h"
#include "proc.h"
#include "spinlock.h"
struct msg {
struct msg *next;
long type;
char *dataaddr;
int datasize;
};
struct mq {
int key;
int status;
struct msg *msgs;
int maxbytes;
int curbytes;
int refcount;
};
struct spinlock mqlock;
struct mq mqs[MQMAX];
struct proc* wqueue[NPROC];
int wstart=0;
struct proc* rqueue[NPROC];
int rstart=0;
void
mqinit()
{
cprintf("mqinit.\n");
initlock(&mqlock,"mqlock");
for(int i =0;i<MQMAX;++i){
mqs[i].status = 0;
}
}
int findkey(int key)
{
int idx =-1;
for(int i = 0;i<MQMAX;++i){
if(mqs[i].status != 0 && mqs[i].key == key){
idx = i;
break;
}
}
return idx;
}
int newmq(int key)
{
int idx =-1;
for(int i=0;i<MQMAX;++i){
if(mqs[i].status == 0){
idx = i;
break;
}
}
if(idx == -1){
cprintf("newmq failed: can not get idx.\n");
return -1;
}
mqs[idx].key = key;
mqs[idx].status = 1;
mqs[idx].msgs = (struct msg*)kalloc();
if(mqs[idx].msgs == 0){
cprintf("newmq failed: can not alloc page.\n");
return -1;
}
memset(mqs[idx].msgs,0,PGSIZE);
mqs[idx].msgs -> next = 0;
mqs[idx].msgs -> datasize = 0;
mqs[idx].maxbytes = PGSIZE;
mqs[idx].curbytes = 16;
mqs[idx].refcount = 1;
proc->mqmask |= 1 << idx;
return idx;
}
int
mqget(uint key)
{
acquire(&mqlock);
int idx = findkey(key);
if(idx != -1){
if(!(proc->mqmask >> idx & 1)){
proc->mqmask |= 1 << idx;
mqs[idx].refcount++;
}
release(&mqlock);
return idx;
}
idx = newmq(key);
release(&mqlock);
return idx;
}
int
msgsnd(uint mqid, void* msg, int sz)
{
if(mqid<0 || MQMAX<=mqid || mqs[mqid].status == 0){
return -1;
}
char *data = (char *)(*((int *) (msg + 4)));
int *type = (int *)msg;
if(mqs[mqid].msgs == 0){
cprintf("msgsnd failed: msgs == 0.\n");
return -1;
}
acquire(&mqlock);
while(1){
if(mqs[mqid].curbytes + sz + 16 <= mqs[mqid].maxbytes){
struct msg *m = mqs[mqid].msgs;
while(m->next != 0){
m = m -> next;
}
m->next = (void *)m + m->datasize + 16;
m = m -> next;
m->type = *(type);
m->next = 0;
m->dataaddr = (void*)m + 16;
m->datasize = sz;
memmove(m->dataaddr, data, sz);
mqs[mqid].curbytes += (sz+16);
for(int i=0; i<rstart; i++)
{
wakeup(rqueue[i]);
}
rstart = 0;
release(&mqlock);
return 0;
} else {
cprintf("msgsnd: can not alloc: pthread: %d sleep.\n",proc->pid);
wqueue[wstart++] = proc;
sleep(proc,&mqlock);
}
}
return -1;
}
int reloc(int mqid)
{
struct msg *pages = mqs[mqid].msgs;
struct msg *m = pages;
struct msg *t;
struct msg *pre = pages;
while (m != 0)
{
t = m->next;
memmove(pages, m, m->datasize+16);
pages->next = (struct msg *)((char *)pages + pages->datasize + 16);
pages->dataaddr = ((char *)pages + 16);
pre = pages;
pages = pages->next;
m = t;
}
pre->next = 0;
return 0;
}
int
msgrcv(uint mqid, void* msg, int sz)
{
if(mqid<0 || MQMAX<=mqid || mqs[mqid].status ==0){
return -1;
}
int *type = msg;
int *data = msg + 4;
acquire(&mqlock);
while(1){
struct msg *m = mqs[mqid].msgs->next;
struct msg *pre = mqs[mqid].msgs;
while (m != 0)
{
if(m->type == *type){
memmove((char *)*data, m->dataaddr, sz);
pre->next = m->next;
mqs[mqid].curbytes -= (m->datasize + 16);
reloc(mqid);
for(int i=0; i<wstart; i++)
{
wakeup(wqueue[i]);
}
wstart = 0;
release(&mqlock);
return 0;
}
pre = m;
m = m->next;
}
cprintf("msgrcv: can not read: pthread: %d sleep.\n",proc->pid);
rqueue[rstart++] = proc;
sleep(proc,&mqlock);
}
return -1;
}
void
rmmq(int mqid)
{
kfree((char *)mqs[mqid].msgs);
mqs[mqid].status = 0;
}
void
releasemq2(int mask)
{
acquire(&mqlock);
for(int id = 0;id<MQMAX;++id){
if( mask >> id & 0x1){
mqs[id].refcount--;
if(mqs[id].refcount == 0){
rmmq(id);
}
}
}
release(&mqlock);
}
void
releasemq(uint key)
{
//cprintf("releasemq: %d.\n",key);
int idx= findkey(key);
if (idx!=-1){
acquire(&mqlock);
mqs[idx].refcount--; //引用数目减1
if(mqs[idx].refcount == 0) //引用数目为0时候需要回收物理内存
rmmq(idx);
release(&mqlock);
}
}
void
addmqcount(uint mask)
{
acquire(&mqlock);
for (int key = 0; key < MQMAX; key++)
{
if(mask >> key & 1){
mqs[key].refcount++;
}
}
release(&mqlock);
}
msg_test.c
#include "param.h"
#include "types.h"
#include "stat.h"
#include "user.h"
#include "fs.h"
#include "fcntl.h"
#include "syscall.h"
#include "traps.h"
#include "memlayout.h"
struct msg{
int type;
char *dataaddr;
}s1,s2,g;
void msg_test()
{
int mqid = mqget(123);
// int msg_len = 48;
// s1.dataaddr = "total number:47 : hello, this is child process.";
int pid = fork();
if(pid == 0){
s1.type = 1;
s1.dataaddr = "This is the first message!";
msgsnd(mqid, &s1, 27);
s1.type = 2;
s1.dataaddr = "Hello, another message comes!";
msgsnd(mqid, &s1, 30);
s1.type = 3;
s1.dataaddr = "This is the third message, and this message has great many characters!";
msgsnd(mqid, &s1, 70);
// sleep(10);
// for(int i=0; i<70; i++)
// {
// s1.type = i;
// msgsnd(mqid, &s1, msg_len);
// }
printf(1,"all messages have been sent.\n");
} else if (pid >0)
{
// sleep(10); // sleep保证子进程消息写入
// g.dataaddr = malloc(msg_len);
// for(int i=0; i<70; i++)
// {
// g.type = i;
// msgrcv(mqid, &g, msg_len);
// printf(1, "读取第%d个消息: %s\n", i, g.dataaddr);
// }
sleep(10); // sleep保证子进程消息写入
g.dataaddr = malloc(70);
g.type = 2;
msgrcv(mqid,&g, 30);
printf(1, "receive the %dth message: %s\n", 2, g.dataaddr);
g.type = 1;
msgrcv(mqid,&g, 27);
printf(1, "receive the %dth message: %s\n", 1, g.dataaddr);
g.type = 3;
msgrcv(mqid,&g, 70);
printf(1, "receive the %dth message: %s\n", 3, g.dataaddr);
wait();
}
exit();
}
int
main(int argc, char *argv[])
{
// printf(1, "消息队列测试\n");
msg_test();
exit();
}
运行结果
2.4 内存管理
2.4.1 实现myfree()和myalloc()系统调用
myalloc.c
#include "types.h"
#include "stat.h"
#include "user.h"
int
main(int argc, char *argv[]) {
// int pid = getpid();
// map(pid);
char* m1 = (char*)myalloc(2 * 4096);
char* m2 = (char*)myalloc(3 * 4096);
char* m3 = (char*)myalloc(1 * 4096);
char* m4 = (char*)myalloc(7 * 4096);
char* m5 = (char*)myalloc(9 * 4096);
m1[0] = 'h';
m1[1] = '\0';
printf(1,"m1:%s\n",m1);
myfree(m2);
//m2[1] = 'p';
myfree(m4);
// map(pid);
sleep(5000);
myfree(m1);
myfree(m3);
myfree(m5);
// char *p=(char *)0x0000;
// for(int i=0x0000;i<0x08;i++)
// *(p+i)='*';
// printf(1,"This string shouldn't be modified!\n");
// exit();
exit();
}
运行结果:
遇见问题:
解决:
03 高级
(待更新)
3.1 实现xv6内核线程
创建线程的开销比创建进程要小,线程可以共享进程的主要资源,例如内存映像、打开的文件等。在xv6上实现线程所涉及的主要工作如下:
- 实现clone系统调用,用于创建一个内核线程
- 实现join系统调用,用于回收一个内核线程
- 实现用户线程库,封装对线程的管理,而用户只需要知道接口即可
- 提供测试样例,包括共享进程空间、多线程并行
注意:建立在2.4.1基础上
3.2 文件系统
文件系统方面主要实现了三个功能
- 为xv6文件系统增加文件读写权限控制
- 实现恢复被删除的文件内容
- 和设备有关的磁盘裸设备的读写
3.3 虚拟内存
实现的虚存交换机制比较简陋,换出的页帧内容保存到磁盘文件系统的普通文件数据区,而不是Linux那样使用独立的交换分区获得交换文件。换出的页帧所在盘块号直接保存在其pte的高位,其pte低12位仍用作标志用途。
演示了在一个进程启动后,分配和使用的内存总量超过系统剩余的总量时,呈现的进程内部的交换过程。