跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第五节 多线程
 
 

        在早期的操作系统中进程一直是独立运行的基本单位,但到了上世纪80年代,人们又提出了比进程更小的,并且可以独立运行的基本单位,这个基本单位被称作为线程。提出线程的目的是为了提高程序的并发执行的程度,从而提高系统的吞吐量。由于线程与线程之间需要进程数据共享和通信,所以一个进程中的多个线程都可以访问其父进程的资源。同时又采用信号量机制来控制多个线程之间的资源共享问题。我们先来学习如何为进程创建一个线程,再通过一个实际的例子来理解它的原理与应用。

        为进程创建一个线程,首先要为使这个线程能够立运行,所以要先为其分配一个pcb,并且与它的父进程共享代码段和页表一页目录,这样做的目的是为了让线程能够调用其它函数和全局资源:

void create_pthread(s_pcb *parent_pcb, s_pthread *p, void *run, void *args)
{
        //进程控制块
        s_pcb *pcb = alloc_page(process_id, pages_of_pcb(), 0, 0);                
        //页目录
        pcb->page_dir = parent_pcb->page_dir;
        //页表
        pcb->page_tbl = parent_pcb->page_tbl;
        //申请栈
        pcb->stack = alloc_page(process_id, P_STACK_P_NUM, 1, 0);                
        if (args != NULL)
        {
                //设置传入参数
                u32 *args_addr = pcb->stack + P_STACK_P_NUM - 4;
                *args_addr = (u32) args;
        }
        //申请0级栈
        pcb->stack0 = alloc_page(process_id, P_STACK0_P_NUM, 0, 0);
        //初始化pcb
        init_pthread(pcb, process_id, run);
        //将此进程加入链表
        pcb_insert(pcb);
        //进程号加一
        process_id++;
}

        初始化线程,为其分配任务号,指定eip运行多线程函数地址,并设定esp为栈地址,设定cr3为页目录地址:

void init_pthread(s_pcb *pcb, u32 pid, void *run)
{
        init_pcb(pcb);

        //进程号
        pcb->process_id = pid;
        //程序入口地址
        pcb->tss.eip = (u32) run;
        //程序栈
        pcb->tss.esp = (u32) pcb->stack + P_STACK_P_NUM - 8;
        //程序0级栈
        pcb->tss.esp0 = (u32) pcb->stack0 + P_STACK0_SIZE;
        //页目录存入到cr3中
        pcb->tss.cr3 = (u32) pcb->page_dir;
        //初始化pcb所在的内存页
        init_process_page((u32) pcb, pages_of_pcb(), pcb->page_dir);
        //初始化pcb->stack0所在的内存页
        init_process_page((u32) pcb->stack0, pages_of_pcb(), pcb->page_dir);
}

       为普通程序提供创建线程的系统调用:

void pthread_create(s_pthread *p, void *function, void *args)
{
        int params[4];
        params[0] = 3;
        params[1] = (int) p;
        params[2] = (int) function;
        params[3] = (int) args;
        __asm__ volatile("int        $0x80" :: "a"(params));
}

        最后来编写一个多线程的例子:一个车站要出售20张车票,需要有2个售票窗口来执行售票工作,这2个窗口售票的过程是相互独立的,但共享同一个票库。每当窗口出售了一张车票,剩余票数减1,当剩余票数为0时停止售票。这是一个非常典型的多线程例子,我们先来看一下它的实现过程:

#define PNUM                 (2)
void sell_ticket(int num)
{
        //调用0x82号中断程序,显示一个数字
        int params[2];
        params[0] = 1;
        params[1] = num;
        __asm__ volatile("int        $0x82" :: "a"(params));
}
void myfunc(void *args)
{
        int *num = (int *) args;
        while (1)
        {
                if ((*num) <= 0)
                {
                        break;
                }
                //模拟等待了一小会
                msleep(10);
                sell_ticket(*num);
                (*num)--;
        }
        for (;;)
        {
        }
}
int main(int argc, char **args)
{
        int num = 20;
        s_pthread p[PNUM];
        for (int i = 0; i < PNUM; i++)
        {
                pthread_create(&p[i], &myfunc, &num);
        }
        for (;;)
        {
        }
        return 0;
}

        上面代码中msleep(10);是模拟一个现实的过程:售票时,售票员先要查询剩余票数,当剩余票数大于0时,售票员按下出售健,剩余票数减1。也就是说在查询到有剩余票后,再对剩余票数减1,这一个过程会有一小会儿的等待过程。编译运行程序并查看程序运行结果:



 

        源代码的下载地址为:

https            https://github.com/magicworldos/lidqos.git 
git              git@github.com:magicworldos/lidqos.git 
subverion        https://github.com/magicworldos/lidqos 
branch           v0.21

        下面让我们把售票窗口数由2修改成10,再来看一下运行结果:



 

        奇怪的事情发生了:总票数为20张,但是10个售票窗口居然一共出售了28张票。并且剩余票数被减为-7。发生这一现象的原因是售票员在“查看剩余票”到“出售”再到“剩余票数减1”这个过程中经过了一小段时间,当售票员A看到剩余票数为1时,A可以出售此车票,但当A还没有按下“出售”按钮时,另外一个售票员B也看到了这张车票,于是他也可以出售此车票。于是就产生了上面的错误,多个售票员同时看到了剩余票数为1,同时“出售”则车票数被减为负数。这就是多线程共享数据时发生的错误。但车票数被减为负数也只是小问题,更重要的问题是对于买票者来说,可能会出现多个人买到了同一张座位的车票。

        为了解决上述问题,我们可以结合上一节所学习的信号量机制来解决这个错误。由于信号量的增减过程是在系统中断服务中完成的,也就是说这是一个不可再进行中断或分割的过程。我们称这个操作过程为“原子操作”,所以对于信号量的增减不会有上述问题中“等待一小会”的问题。我们来修改一下代码,在售票前后加入信号量的P/V操作:

//全局售票信号量
#define PNUM                 (10)
s_sem sem;
void sell_ticket(int num)
{
        //调用0x82号中断程序,显示一个数字
        int params[2];
        params[0] = 1;
        params[1] = num;
        __asm__ volatile("int        $0x82" :: "a"(params));
}
void myfunc(void *args)
{
        int *num = (int *) args;
        while (1)
        {
                //信号量P操作
                sem_wait(&sem);
                //剩余票数为0时停止售票
                if ((*num) <= 0)
                {
                        break;
                }
                //模拟等待了一小会
                msleep(10);
                //售票
                sell_ticket(*num);
                //剩余票数减1
                (*num)--;
                //信号量V操作
                sem_post(&sem);
        }
        //信号量V操作
        sem_post(&sem);
        for (;;)
        {
        }
}

int main(int argc, char **args)
{
        //初始化信号量
        sem_init(&sem, 1);
        //剩余票数
        int num = 20;
        s_pthread p[PNUM];
        //创建多个线程
        for (int i = 0; i < PNUM; i++)
        {
                pthread_create(&p[i], &myfunc, &num);
        }
        for (;;)
        {
        }
        return 0;
}

        再来看一下运行结果:



 

        运行结果正确。
 

        源代码的下载地址为:

https            https://github.com/magicworldos/lidqos.git 
git              git@github.com:magicworldos/lidqos.git 
subverion        https://github.com/magicworldos/lidqos 
branch           v0.22

 

    返回首页    返回顶部
  看不清?点击刷新

 

  Copyright © 2015-2023 问渠网 辽ICP备15013245号