操作系统 实验三 同步与通信 16281002_杜永坤

实验三 同步与通信

实验目的
系统调用的进一步理解。
进程上下文切换。
同步的方法。
实验题目

1)通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。

代码:

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
#include "unistd.h"
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *arry;
};

static int sem_id = 0,sem_id1=0,sem_id2=0,sem_id3=0,sem_id4=0,sem_id5=0,sem_id6=0,sem_id7=0,sem_id8=0;
static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();
   

int main(int argc, char *argv[])
{
    //char message = 'X';
    //int i = 0;
pid_t p1,p2,p3,p4;

sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
    sem_id1 = semget((key_t)1235, 1, 0666 | IPC_CREAT);
    
    sem_id3 = semget((key_t)1237, 1, 0666 | IPC_CREAT);
    
    sem_id5 = semget((key_t)1239, 1, 0666 | IPC_CREAT);
    sem_id6 = semget((key_t)1240, 1, 0666 | IPC_CREAT);
    sem_id7 = semget((key_t)1241, 1, 0666 | IPC_CREAT);
    sem_id8 = semget((key_t)1242, 1, 0666 | IPC_CREAT);
set_semvalue(sem_id);
    set_semvalue(sem_id1);
    
    set_semvalue(sem_id3);
    
    set_semvalue(sem_id5);
    set_semvalue(sem_id6);
    set_semvalue(sem_id7);
    set_semvalue(sem_id8);
    printf("%d %d %d %d \n",sem_id1,sem_id2,sem_id3,sem_id4);
    semaphore_p(sem_id1);
    semaphore_p(sem_id3);
    
    semaphore_p(sem_id5);
    semaphore_p(sem_id6);
    semaphore_p(sem_id7);
    semaphore_p(sem_id8);
    //创建信号量

    
     p1=fork();  p2=fork();p3=fork();p4=fork();
    if (p1 < 0) 
printf("error in fork!"); 
else if (p1 == 0) 
    
    {
    
     semaphore_p(sem_id);
printf("i am p1, my process id is %d\n",getpid()); 
  semaphore_v(sem_id1);
  semaphore_v(sem_id8);
}
    if (p2 < 0) 
printf("error in fork!"); 
else if (p2 == 0) 
    {

        semaphore_p(sem_id1); 
printf("i am p2, my process id is %d\n",getpid()); 
  semaphore_v(sem_id3);
semaphore_v(sem_id7);

} 
    if (p3 < 0) 
printf("error in fork!"); 
else if (p3 == 0) 
    { semaphore_p(sem_id1);
printf("i am p3, my process id is %d\n",getpid()); 
  semaphore_v(sem_id3);
semaphore_v(sem_id6);
} 
if (p4 < 0) 
printf("error in fork!"); 
else if (p4 == 0) 
    { semaphore_p(sem_id3);
printf("i am p4, my process id is %d\n",getpid()); 
semaphore_v(sem_id5);
} 
    semaphore_p(sem_id5);
    semaphore_p(sem_id6);
    semaphore_p(sem_id7);
    semaphore_p(sem_id8);

    del_semvalue(sem_id);
    del_semvalue(sem_id1);
    
    del_semvalue(sem_id3);
    
    del_semvalue(sem_id5);
    del_semvalue(sem_id6);
    del_semvalue(sem_id7);
    del_semvalue(sem_id8);
    exit(EXIT_SUCCESS);

    
}
static int set_semvalue(int sem_id)
{
    //用于初始化信号量,在使用信号量前必须这样做
    union semun sem_union;
    sem_union.val = 1;
    if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
        return 0;
    return 1;
}
static void del_semvalue(int sem_id)
{
    //删除信号量
    union semun sem_union;
    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
        //fprintf(stderr, "Failed to delete semaphore\n");
        ;
}
static int semaphore_p(int sem_id)
{
    //对信号量做减1操作,即等待P(sv)
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = -1;//P()
    sem_b.sem_flg = SEM_UNDO;
    if(semop(sem_id, &sem_b, 1) == -1)
    {
        //fprintf(stderr, "semaphore_p failed\n");
        return 0;
    }
    return 1;
}
static int semaphore_v(int sem_id)
{
    //这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;//V()
    sem_b.sem_flg = SEM_UNDO;
    if(semop(sem_id, &sem_b, 1) == -1)
    {
        //fprintf(stderr, "semaphore_v failed\n");
        return 0;
    }
    return 1;
}

根据实验要求:
P2,p3互斥执行,我采用p2执行后p3不能执行,p3执行后p2不能执行的模式
下面是结果:
在这里插入图片描述
代码解释:

sem_id1 = semget((key_t)1235, 1, 0666 | IPC_CREAT);
    
    sem_id3 = semget((key_t)1237, 1, 0666 | IPC_CREAT);
    
    sem_id5 = semget((key_t)1239, 1, 0666 | IPC_CREAT);
    sem_id6 = semget((key_t)1240, 1, 0666 | IPC_CREAT);
    sem_id7 = semget((key_t)1241, 1, 0666 | IPC_CREAT);
    sem_id8 = semget((key_t)1242, 1, 0666 | IPC_CREAT);

5,6,7,8四个信号量是用来限制关闭信号量动作的,如果在一个进程中关闭了信号量,那么其他信号量的使用就会收到影响,所以采用四个信号量进行限制信号量的关闭。

2)火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。(说明:为了更容易产生并发错误,可以在适当的位置增加一些pthread_yield(),放弃CPU,并强制线程频繁切换,例如售票线程的关键代码:
temp=ticketCount;
pthread_yield();
temp=temp-1;
pthread_yield();
ticketCount=temp;
退票线程的关键代码:
temp=ticketCount;
pthread_yield();
temp=temp+1;
pthread_yield();
ticketCount=temp;

根据实验要求编写代码:

#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
pthread_mutex_t mutex ;
int ticket_counter;
int producer_counter;
int consumer_counter;
int all_pro;
int all_con;

void *producer( int i ) {
    //printf("this is producer");
    while(--i>0){
        pthread_mutex_lock( &mutex );
        sched_yield();
        ticket_counter++;
        sched_yield();
       
        pthread_mutex_unlock( &mutex );
    }
    pthread_exit( NULL );
}

void *consumer( int j ) {
    while(j-->0){
        pthread_mutex_lock( &mutex );
        sched_yield();
        ticket_counter--;
        sched_yield();
      
        pthread_mutex_unlock( &mutex );
    }
    pthread_exit( NULL );
}

void create_and_join_Pthread(){
    all_con=0;all_pro=0;
    pthread_t *pro, *con;
    pro=(pthread_t*)malloc(sizeof(pthread_t)*producer_counter);
    con=(pthread_t*)malloc(sizeof(pthread_t)*consumer_counter);
    printf("%d个线程(生产者)产生的票数为: \n",producer_counter);
    srand((unsigned)time(NULL));
    for(int i=0;i<producer_counter;i++){
        int a = rand() % 20 + 1;
        all_pro+=a;
        printf("%d: %d \n",i+1,a);
        pthread_create( &pro[i], NULL, producer, a );
    }
    printf("产生的票数为:%d",all_pro);
    printf("\n");
    printf("%d个线程(消费者)售出的票数为: \n",consumer_counter);
    for(int i=0;i<consumer_counter;i++){
        int a = rand() % 20 + 1;
        all_con+=a;
        printf("%d: %d \n",i+1,a);
        pthread_create( &con[i], NULL, consumer, a );
    }
    printf("消费的的票数为:%d",all_pro);
    printf("\n");
    for(int i=0;i<producer_counter;i++){
        pthread_join( pro[i], NULL );
    }
    for(int i=0;i<producer_counter;i++){
        pthread_join( con[i], NULL );
    }
}

int main() {
    printf("输入产生车票线程数 销售车票线程数 开始时总票数 :");
    scanf("%d %d %d",&producer_counter,&consumer_counter,&ticket_counter);
//    ticket_counter=10000;
//    producer_counter=12;
//    consumer_counter=2;
    int temp=ticket_counter;
    pthread_mutex_init(&mutex, NULL);
    create_and_join_Pthread();
    printf("应有余票%d,其中售出%d,生产%d.\n",temp-all_con+all_pro,all_con,all_pro);
    printf("\n实际余票为%d\n",ticket_counter+producer_counter);
    return 0;
}

在这里插入图片描述在这里插入图片描述
当添加sched_yield()时,多次运行程序,得到结果如下。发现错误概率很小,也不一定会出错,但是错误是一定存在的:
这是验证了多次出现的错误结果:

在这里插入图片描述

3)一个生产者一个消费者线程同步。设置一个线程共享的缓冲区, char buf[10]。一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。要求输出的和输入的字符和顺序完全一致。(在输出线程中,每次输出睡眠一秒钟,然后以不同的速度输入测试输出是否正确)。要求多次测试添加同步机制前后的实验效果。

根据实验结果编写实验代码:

 #include <stdio.h>
 #include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
 volatile int counter = 0;
 int loops;
char buf[10];
int i=0;
int j=0;
sem_t *myp1=NULL;
sem_t *myp2=NULL;
/*
 void *worker(void *arg) {
 int i;
 for (i = 0; i < loops; i++) {
sem_wait(myp);
 counter++;
sem_post(myp);
 }
 return NULL;
 }
*/
 void *worker1(void *arg) {

while(1)
{
	
sem_wait(myp1);
scanf("%c",&buf[i]);
sem_post(myp2);


}


 return NULL;
 }
 void *worker2(void *arg) {

while(1)
{

{
	sem_wait(myp2);
	printf("输出:%c\n",buf[j]);
		
	sem_post(myp1);
	
}

}
 return NULL;
 }
 int main(int argc, char *argv[])
 {
 /*if (argc != 2) {
 fprintf(stderr, "usage: threads <value>\n");
 exit(1);
 }
 loops = atoi(argv[1]);*/

myp1 = sem_open("myp1",O_CREAT,0666,1);
myp2 = sem_open("myp2",O_CREAT,0666,0);
 pthread_t p1, p2;
// printf("Initial value : %d\n", counter);

 pthread_create(&p1, NULL, worker1, NULL);
 pthread_create(&p2, NULL, worker2, NULL);
 pthread_join(p1, NULL);
 pthread_join(p2, NULL);
 	//printf("Final value : %d\n", counter);
sem_unlink("myp1");
sem_unlink("myp2");
 	return 0;
 }		

实验结果:
在这里插入图片描述
在输入字符的时候,由于scanf的缓存机制,使得可以输入多个字符,当输入回车后,scanf函数运行结束,当运行结束时,释放了输入线程的信号量,这就使得printf开始工作,输出了缓存空间的字符
4)
1、在Pinto操作系统中,增加一个系统调用,系统调用名为test_system_call()。无输入参数,输出为在显示器中打印输出:Hello. This is my test system call.

2、进程通信问题。阅读并运行共享内存、管道、消息队列三种机制的代码

(参见
https://www.cnblogs.com/Jimmy1988/p/7706980.html
https://www.cnblogs.com/Jimmy1988/p/7699351.html
https://www.cnblogs.com/Jimmy1988/p/7553069.html

实验测试a)通过实验测试,验证共享内存的代码中,receiver能否正确读出sender发送的字符串?如果把其中互斥的代码删除,观察实验结果有何不同?如果在发送和接收进程中打印输出共享内存地址,他们是否相同,为什么?b)有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?c)消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
a)通过实验测试,验证共享内存的代码中,receiver能否正确读出sender发送的字符串?如果把其中互斥的代码删除,观察实验结果有何不同?如果在发送和接收进程中打印输出共享内存地址,他们是否相同,为什么?

验证代码。编译代码后,将将两个进程按照先sender、后receive的顺序进行启动:
在这里插入图片描述
然后在sender的进程中输入字符串,可以看到receiver的进程正常接收sender进程输入的字符,并打印到终端上。
在这里插入图片描述

阅读代码找到两个进程的互斥信号量的代码:

struct sembuf sem_b;
  sem_b.sem_num = 0;      //first sem(index=0)
  sem_b.sem_flg = SEM_UNDO;
  sem_b.sem_op = 1;           //Increase 1,make sem=1

将两个进程的这部分代码进行注释,而且要将与sem_b相关的代码进行注释,然后再次编译,再次运行:
在这里插入图片描述
观察两个进程的输出结果:
当两个进程开始运行,sender没有输入字符,receiver进程循环不断地输出提示,sender进程正常提示,等待字符的输入,但是receiver进程不输出字符串,当sender进程输入了字符串,receiver进程会循环不断地输出sender进程输入的字符,当在sender 进程输入end,两个进程正常结束。
可以观察分析得到,当两个互斥的进程将控制互斥的信号量进行互斥,会发现互斥关系不存在了,就会造成两个进程的运行没有了控制。而对于两个进程的共享的数据,两个就进程还是正常的使用,所以receiver会输出sender输入的字符。

   //5. attach the shm_id to this process
    char *shm_ptr;
shm_ptr = shmat(shm_id, NULL, 0);

Shm_ptr就是两个进程的共享变量。
c)Shm_ptr就是两个进程的共享变量,所以两个进程的共享内存就是shm_ptr的地址:
将shm_ptr的地址打印出来,加入代码:
Printf(“%d \n”,shm_ptr);
编译运行:
在这里插入图片描述
可以发现两个进程使用共享内存地址是不同的。
这是使用了shmat函数的原因:
shmat(把共享内存区对象映射到调用进程的地址空间)
函数说明
连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg)
函数传入值msqid共享内存标识符shmaddr指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置。
该函数将两个进程的地址空间进行链接,形成共享地址空间。所以两个进程的共享的地址空间地址是不一样的。
b)有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
有名管道代码验证:
实验结果:
在这里插入图片描述
无名管道实验验证:
运行结果
在这里插入图片描述

无名管道:
无名管道通行作为Linux进程通信中的一种,是UNXI系统IPC中最古老的一种。对于无名管道来说,他有如下特点:
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间),实现依赖父子进程文件共享。
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
它是阻塞读写的。
对于Linux管道文件来说,可以使用两种读写方式:阻塞读写与非阻塞读写。所谓阻塞读写,是无论是先读还是先写都要等到另一个操作才能离开阻塞。也就是:如果先读,陷入阻塞,等待写操作;如果先写,陷入阻塞,等待读操作。对应的有非阻塞读写,它们无须等待另一个操作的,直接执行read()或者write()能读就读,能写就写,不能就返回-1。
有名管道:
对于有名管道的示例程序来说,运行过程中,先运行写端后运行读端,当在运行写端输入内容并输入回车键之后,读端读出管道文件中的数据,并在同一时刻与写端一起结束,这种运行方式下,可以完成给定的要求。但是,如果把两端的运行顺序更换,变成先运行读端后运行写端,那么可以清楚的发现,给定功能会失效,对于写端来说,甚至不会输出提示信息。由此我们可以推断,有名管道的也是阻塞读写的,当读端占据了管道后,写端无法进入,该流程就无法达到原有的效果。换言之,该程序正确运行顺序为先运行写端,再运行读端,最后在写端内写入数据并输入回车,否则程序会因为初始的管道中无数据且先进入的读端阻塞了写端导致失效。
c)对于消息队列而言,它的机制非常类似于3)题中的输入和输出程序。可以推断,消息队列也是阻塞读写的。而且它还必须拥有队空阻塞读和队满阻塞写的机制,才可以真正的实现完整的传输机制。针对这样的推断进行实验,发现是正确的。
示例代码运行结果:
在这里插入图片描述

5)阅读Pintos操作系统,找到并阅读进程上下文切换的代码,说明实现的保存和恢复的上下文内容以及进程切换的工作流程。

参考博客:
https://www.cnblogs.com/laiy/p/pintos_project1_thread.html
上下文切换的代码位于/threads/switch.h和/threads/switch.S中。首先看一下timer_sleep函数(/devices/timer.c)。timer_sleep函数在devices/timer.c。系统现在是使用busy wait实现的,即线程不停地循环,直到时间片耗尽。更改timer_sleep的实现方式。
我们先来看一下devices目录下timer.c中的timer_sleep实现:
让我们一行一行解析:

/* Sleeps for approximately TICKS timer ticks.  Interrupts must
   be turned on. */
void
timer_sleep (int64_t ticks)
{
  int64_t start = timer_ticks ();
  ASSERT (intr_get_level () == INTR_ON);
  while (timer_elapsed (start) < ticks)
    thread_yield();
}

第6行: 调用了timer_ticks函数, 让我们来看看这个函数做了什么。

/* Sleeps for approximately TICKS timer ticks.  Interrupts must
   be turned on. */
void
timer_sleep (int64_t ticks)
{
  int64_t start = timer_ticks ();
  ASSERT (intr_get_level () == INTR_ON);
  while (timer_elapsed (start) < ticks)
    thread_yield();
}

然后我们注意到这里有个intr_level的东西通过intr_disable返回了一个东西,没关系,我们继续往下找。

/* Interrupts on or off? */
enum intr_level 
  {
    INTR_OFF,             /* Interrupts disabled. */
    INTR_ON               /* Interrupts enabled. */
  };
/* Disables interrupts and returns the previous interrupt status. */
enum intr_level
intr_disable (void) 
{
  enum intr_level old_level = intr_get_level ();

  /* Disable interrupts by clearing the interrupt flag.
     See [IA32-v2b] "CLI" and [IA32-v3a] 5.8.1 "Masking Maskable
     Hardware Interrupts". */
  asm volatile ("cli" : : : "memory");

  return old_level;
}

这里很明显,intr_level代表能否被中断,而intr_disable做了两件事情:1. 调用intr_get_level() 2. 直接执行汇编代码,调用汇编指令来保证这个线程不能被中断。
这个函数一样是调用了汇编指令,把标志寄存器的东西放到处理器棧上,然后把值pop到flags(代表标志寄存器IF位)上,通过判断flags来返回当前终端状态(intr_level)。
至此,函数嵌套了这么多层,整理逻辑:
intr_get_level返回了intr_level的值
intr_disable获取了当前的中断状态, 然后将当前中断状态改为不能被中断, 然后返回执行之前的中断状态。

有以上结论我们可以知道: timer_ticks的intr_get_level做了如下的事情: 禁止当前行为被中断, 保存禁止被中断前的中断状态(用old_level储存)。

/* Returns the current interrupt status. */
/* Enables or disables interrupts as specified by LEVEL and
   returns the previous interrupt status. */
enum intr_level
intr_set_level (enum intr_level level) 
{
  return level == INTR_ON ? intr_enable () : intr_disable ();
}

对于timer_ticks剩下的内容来说,就是用t获取了一个全局变量ticks, 然后返回, 其中调用了set_level函数。set_level的实现机制,根据上述内容容易得出: 如果之前是允许中断的(INTR_ON)则enable否则就disable。intr_enable的实现和之前基本一致:

/* Enables interrupts and returns the previous interrupt status. */
enum intr_level
intr_enable (void) 
{
  enum intr_level old_level = intr_get_level ();
  ASSERT (!intr_context ());

  /* Enable interrupts by setting the interrupt flag.

     See [IA32-v2b] "STI" and [IA32-v3a] 5.8.1 "Masking Maskable
     Hardware Interrupts". */
  asm volatile ("sti");

  return old_level;
}

这里直接返回了是否外中断的标志in_external_intr,就是说ASSERT断言这个中断不是外中断(IO等, 也称为硬中断)而是操作系统正常线程切换流程里的内中断(也称为软中断)。至此,我们可以分析出Pintos系统保持原子性的语句,即使用如下的语句包裹:

/* Enables interrupts and returns the previous interrupt status. */
enum intr_level old_level = intr_disable ();
...
intr_set_level (old_level);

对于ticks来说,从pintos被启动开始,ticks就一直在计时,代表着操作系统执行单位时间的前进计量。现在回过来看timer_sleep这个函数,start获取了起始时间,然后断言必须可以被中断,不然会一直死循环下去。
然后就是如下循环:

/* Enables interrupts and returns the previous interrupt status. */
while (timer_elapsed (start) < ticks)
thread_yield();

注意这个ticks是函数的形参不是全局变量,然后看一下这两个函数:对于timer_elapsed来说,它返回了当前时间距离then的时间间隔,那么这个循环实质就是在ticks的时间内不断执行thread_yield。最后来看thread_yield:

/* Yields the CPU.  The current thread is not put to sleep and
   may be scheduled again immediately at the scheduler's whim. */
void
thread_yield (void)
{
  struct thread *cur = thread_current ();
  enum intr_level old_level;

  ASSERT (!intr_context ());

  old_level = intr_disable ();
  if (cur != idle_thread)
    list_push_back (&ready_list, &cur->elem);
  cur->status = THREAD_READY;
  schedule ();
  intr_set_level (old_level);
}

thread_current很明显是返回当前线程起始指针位置。第9行断言是个软中断,第11行至第16包裹起来的就是我们之前分析的线程机制保证的一个原子性操作。对于第12行到15行,如何当前线程不是空闲的线程就调用list_push_back把当前线程的元素扔到就绪队列里面, 并把线程改成THREAD_READY状态。
15行调用schedule:

/* Schedules a new process.  At entry, interrupts must be off and
   the running process's state must have been changed from
   running to some other state.  This function finds another
   thread to run and switches to it.

   It's not safe to call printf() until thread_schedule_tail()
   has completed. */
static void
schedule (void)
{
  struct thread *cur = running_thread ();
  struct thread *next = next_thread_to_run ();
  struct thread *prev = NULL;

  ASSERT (intr_get_level () == INTR_OFF);
  ASSERT (cur->status != THREAD_RUNNING);
  ASSERT (is_thread (next));

  if (cur != next)
    prev = switch_threads (cur, next);
  thread_schedule_tail (prev);
}

可以看出,首先获取当前线程cur和调用next_thread_to_run获取下一个要run的线程,如果就绪队列空闲直接返回一个空闲线程指针, 否则拿就绪队列第一个线程出来返回。如果当前线程和下一个要跑的线程不是同一个的话调用switch_threads返回给prev。

 /* Switches from CUR, which must be the running thread, to NEXT,
    which must also be running switch_threads(), returning CUR in
    NEXT's context. */
struct thread *switch_threads (struct thread *cur, struct thread *next);

这个函数实现是用汇编语言实现的在threads/switch.S里。分析一下这个汇编代码: 先4个寄存器压栈保存寄存器状态(保护作用), 这4个寄存器是switch_threads_frame的成员,然后全局变量thread_stack_ofs记录线程和栈之间的间隙,我们都知道线程切换有个保存现场的过程,先把当前的线程指针放到eax中,并把线程指针保存在相对基地址偏移量为edx的地址中,切换到下一个线程的线程棧指针,保存在ecx中,再把这个线程相对基地址偏移量edx地址(上一次保存现场的时候存放的)放到esp当中继续执行。这里ecx,eax起容器的作用,edx指向当前现场保存的地址偏移量。简单来说就是保存当前线程状态,恢复新线程之前保存的线程状态。然后再把4个寄存器拿出来,这个是硬件设计要求的,必须保护switch_threads_frame里面的寄存器才可以destroy掉eax, edx, ecx。然后注意到现在eax(函数返回值是eax)就是被切换的线程栈指针。我们由此得到一个结论,schedule先把当前线程丢到就绪队列,然后把线程切换如果下一个线程和当前线程不一样的话。
至此,schedule的重点部分分析完成。逻辑继续向上回溯:
thread_schedule_tail其实就是获取当前线程,分配恢复之前执行的状态和现场,如果当前线程死了就清空资源。
schedule其实就是拿下一个线程切换过来继续run。
thread_yield其实就是把当前线程扔到就绪队列里,然后重新schedule,注意这里如果ready队列为空的话当前线程会继续在cpu执行。
最后回溯到我们最顶层的函数逻辑:timer_sleep就是在ticks时间内,如果线程处于running状态就不断把他扔到就绪队列不让他执行。
我们对原来的timer_sleep的实现方式有了十分清楚的理解了,我们也很清楚的看到了它的缺点:此时的线程不断在cpu就绪队列和running队列之间来回(包括整个线程的上下文切换也是其中的一个部分),占用了cpu资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值