我们在创建进程和线程时,并没有为程序设置停止的条件,start_main()函数和线程函数的结尾总有一个无休止的for循环。这是因为当进程或线程结束之后我们需要释放相关的内存资源,将已经申请过的内存块释放掉。需要注意的是其中线程是与进程共享代码段,所以在释放内存时需要先释放子线程的资源再释放父进程的资源。否则如果先释放父进程资源时,子线程还在运行,将无法使用内存相关段的错误。为start_main()函数加入停止进程的中断请求:
void start_main() { int argc = 0; char **args = NULL; main(argc, args); int params[2]; //type为4,代表停止进程 params[0] = 4; //请求0x80号中断服务 __asm__ volatile("int $0x80" :: "a"(params)); }
再编写一个start_pthread()函数用于调用线程函数和停止线程:
extern int pthread_function(void *args); void start_pthread() { void *args = (void *)0; pthread_function(args); int params[2]; //type为4,代表停止进程 params[0] = 4; //请求0x80号中断服务 __asm__ volatile("int $0x80" :: "a"(params)); }
可以看到这个函数与start_main()函数没有什么本质的区别,但它的还是有区别于start_main()的。我们需要一个函数来调用线程函数,并且在线程函数运行结束之后还要调用0x80号中断服务来停止线程。由于start_main()是用来调用main()函数的,而start_pthread()是用来调用线程函数的,所以单独编写一个调用线程的程序,并将其编译成可重定位的文件。在系统内核安装多任务时载入这个程序并为其重定位:
//从文件系统读入程序 s_file *fp = f_open("/usr/bin/start_pthread", FS_MODE_READ, 0, 0); //申请页面用于存放程序代码 start_pthread_data = alloc_page(process_id, 1, 0, 0); //读入程序 f_read(fp, fp->fs.size, (u8 *) start_pthread_data); //程序大小 u32 run_size = fp->fs.size; //关闭文件 f_close(fp); start_pthread_data_offset = relocation_elf(start_pthread_data);
当创建一个新的线程时,则为这个线程的pcb分配一个启动程序内存页面,并将start_pthread 程序复制到这个页面中,再将call命令和call命令的参数设置为正确的内容:
//程序入口函数start_pthread所在内存 pcb->run = alloc_page(process_id, 1, 0, 0); //一个页面 pcb->run_size = 0x1000; //复制start_pthread函数内容到pcb->run中 mmcopy(start_pthread_data + start_pthread_data_offset, pcb->run, 0x1000); //如果有参数 if (args != NULL) { //设置传入参数 u32 *args_addr = pcb->run + 9; *args_addr = (u32) args; } //设置call pthread_function的固定位置 u32 *pthread_function = pcb->run + 0x14; *pthread_function = (run - (pcb->run + 0x14) – 4);
为pcb中加入两个属性,一个是父进程指针一个是子进程/线程指针:
typedef struct process_control_block { //进程号 u32 process_id; //类型,进程、线程 u32 type_pt; //任务描述段 s_tss tss; //代码段和数据段的局部描述符 s_gdt ldt[2]; ... ... //父进程/线程 void *parent; //子进程/线程 void *children; } s_pcb;
创建进程时为其初始化父进程和子进程。因为一个线程要与其父进程共享代码段,所以其父进程必须是一个进程,而不是线程:
//线程的父进程 while (parent_pcb->parent != NULL) { parent_pcb = parent_pcb->parent; } //设置父进程线程 pcb->parent = parent_pcb; s_list *p_list = alloc_mm(sizeof(s_list)); p_list->node = pcb; //设置其父进程线程的子线程为当前线程 parent_pcb->children = list_insert_node(parent_pcb->children, p_list);
实现停止进程的中断服务函数:
void pcb_stop(s_pcb *pcb) { s_list *list_node = NULL; //从运行链表中移出此进程 list_pcb = list_remove_node(list_pcb, pcb, &list_node); //加入到停止链表 list_pcb_stop = list_insert_node(list_pcb_stop, list_node); //执行一次调度,跳过当前进程 schedule(); }
在时钟中断时调用pcb_free()函数来释放已停止的进程资源:
void pcb_free() { s_list *p = list_pcb_stop; while (p != NULL) { s_list *pf = p; p = p->next; s_pcb *pcb = (s_pcb *) pf->node; if (pcb->children == NULL) { s_list *list_node = NULL; list_pcb_stop = list_remove_node(list_pcb_stop, pcb, &list_node); if (pcb->parent != NULL) { s_pcb *parent = pcb->parent; s_list *list_n = NULL; //把当前pcb从其父进程的children中移出 parent->children = list_remove_node(parent->children, pcb, &list_n); } u32 pid = pcb->process_id; free_page_by_pid(pid); free_mm(pf, sizeof(s_list)); printf("free process %d\n", pid); } } }
使用if (pcb->children == NULL)来判断一个进程是否有子进程,只有其所有的子进程全部停止并释放之后才可以释放进程。使用printf("free process %d\n", pid);来显示进程/线程资源释放的情况。编译运行并查看结果:
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git git git@github.com:magicworldos/lidqos.git subverion https://github.com/magicworldos/lidqos branch v0.23
Copyright © 2015-2023 问渠网 辽ICP备15013245号