目录

 

一.实验目的

二.实验题目

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

2)火车票

3)一个生产者一个消费者线程同步。

4)进程通信问题

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


一.实验目的

1、系统调用的进一步理解;2、进程上下文切换;3、同步与通信方法。

二.实验题目

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

1、进程前驱图

操作系统实验三

2、程序代码

设置三个信号量,初始值均为0,分别用来记录进程1、2、3是否执行。

起初使用while(P2!= 0)语句判断是否进入子进程,发现程序会顺序执行而非互斥。于是改为if判断。

操作系统实验三

操作系统实验三

3、运行结果

操作系统实验三

运行的整个流程,因为没关闭进程所以P1会一直执行(题目未做要求),但能够按照P1-P3-P2-P4顺序进行。多次试验结果相同。

4、为体现互斥,在进程3中加入sleep()函数,再次运行。

操作系统实验三

此时P2先于P3。证明互斥。

2)火车票

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

1、程序代码

起初将循环写在了sold、cancel内,发现线程顺序执行,并不是预期的同步,于是修改代码将while写入主函数,创建多个线程。

操作系统实验三

操作系统实验三

操作系统实验三

 

问题:在使用pthread_yield()发生编译错误,显示未声明

操作系统实验三

查询得知要在编译时加上-D_GNU_SOURCE选项

操作系统实验三

 

2、运行结果

操作系统实验三

操作系统实验三

结果正确。初始值与最终值相同。

 

3、为了测试信号量作用,将信号量代码删去再次运行

操作系统实验三

操作系统实验三

结果错误。在运行过程中出现读脏数据、不可重复读的现象。例如测试中,卖出时读取的数据并没有更新退还后数据。可见互斥信号量能够保证同步机制。

 

3)一个生产者一个消费者线程同步。

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

 

1、程序代码

经典的生产者消费者问题,非互斥,设置两个信号量。

empty:代表缓冲区空闲数,即空间

full:代表缓冲区填充数,即数据

操作系统实验三

操作系统实验三

2、运行结果

操作系统实验三

慢速输入时如上。

 

操作系统实验三

快速输入时,仍旧可以按序输出。

 

4)进程通信问题

阅读并运行共享内存、管道、消息队列三种机制的代码

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发送的字符串?如果把其中互斥的代码删除,观察实验结果有何不同?如果在发送和接收进程中打印输出共享内存地址,他们是否相同,为什么?

1、代码如下:

/*
 * Filename: Sender.c
 * Description: 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.h>

int main(int argc, char *argv[])
{
    key_t  key;
    int shm_id;
    int sem_id;
    int value = 0;

    //1.Product the key
    key = ftok(".", 0xFF);

    //2. Creat semaphore for visit the shared memory
    sem_id = semget(key, 1, IPC_CREAT|0644);
    if(-1 == sem_id)
    {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    //3. init the semaphore, sem=0
    if(-1 == (semctl(sem_id, 0, SETVAL, value)))
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }

    //4. Creat the shared memory(1K bytes)
    shm_id = shmget(key, 1024, IPC_CREAT|0644);
    if(-1 == shm_id)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    //5. attach the shm_id to this process
    char *shm_ptr;
    shm_ptr = shmat(shm_id, NULL, 0);
    if(NULL == shm_ptr)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    //6. Operation procedure
    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
    
    while(1)
    {
        if(0 == (value = semctl(sem_id, 0, GETVAL)))
        {
            printf("\nNow, snd message process running:\n");
            printf("\tInput the snd message:  ");
            scanf("%s", shm_ptr);

            if(-1 == semop(sem_id, &sem_b, 1))
            {
                perror("semop");
                exit(EXIT_FAILURE);
            }
        }

        //if enter "end", then end the process
        if(0 == (strcmp(shm_ptr ,"end")))
        {
            printf("\nExit sender process now!\n");
            break;
        }
    }

    shmdt(shm_ptr);

    return 0;
}
/*
 * Filename: Receiver.c
 * Description: 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.h>

int main(int argc, char *argv[])
{
    key_t  key;
    int shm_id;
    int sem_id;
    int value = 0;

    //1.Product the key
    key = ftok(".", 0xFF);

    //2. Creat semaphore for visit the shared memory
    sem_id = semget(key, 1, IPC_CREAT|0644);
    if(-1 == sem_id)
    {
        perror("semget");
        exit(EXIT_FAILURE);
    }

    //3. init the semaphore, sem=0
    if(-1 == (semctl(sem_id, 0, SETVAL, value)))
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }

    //4. Creat the shared memory(1K bytes)
    shm_id = shmget(key, 1024, IPC_CREAT|0644);
    if(-1 == shm_id)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    //5. attach the shm_id to this process
    char *shm_ptr;
    shm_ptr = shmat(shm_id, NULL, 0);
    if(NULL == shm_ptr)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    //6. Operation procedure
    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
    
    while(1)
    {
        if(1 == (value = semctl(sem_id, 0, GETVAL)))
        {
            printf("\nNow, receive message process running:\n");
            printf("\tThe message is : %s\n", shm_ptr);

            if(-1 == semop(sem_id, &sem_b, 1))
            {
                perror("semop");
                exit(EXIT_FAILURE);
            }
        }

        //if enter "end", then end the process
        if(0 == (strcmp(shm_ptr ,"end")))
        {
            printf("\nExit the receiver process now!\n");
            break;
        }
    }

    shmdt(shm_ptr);
    //7. delete the shared memory
    if(-1 == shmctl(shm_id, IPC_RMID, NULL))
    {
        perror("shmctl");
        exit(EXIT_FAILURE);
    }

    //8. delete the semaphore
    if(-1 == semctl(sem_id, 0, IPC_RMID))
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }

    return 0;
}

 

2、运行结果

操作系统实验三

能够实时发送与接收消息。

 

3、删去互斥代码

sender:

操作系统实验三

receiver:

操作系统实验三

 

4、再次运行

操作系统实验三

sender无变化,但receiver不再等待发送端消息,直接不停输出共享内存中的数据,

 

5、打印输出共享内存地址

printf("\tsender addr : %p\n", shm_ptr);

printf("\treceiver addr : %p\n", shm_ptr);

操作系统实验三

操作系统实验三

地址不同。

解释:此题与实验一中共享类似。操作系统虚拟化了内存。每个进程访问自己的私有虚拟地址空间,操作系统以某种方式映射到机器的物理内存。一个正在运行的程序中的内存引用不会影响其他进程(或OS本身)的地址空间。实际上他们物理内存地址是相同的,是由操作系统管理的共享资源。

若关闭空间地址随机化,可以看到地址相同。

操作系统实验三

操作系统实验三

 

b)有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?

1、无名管道示例程序代码及运行结果:

#include <stdio.h>
#include <unistd.h>     //for pipe()
#include <string.h>     //for memset()
#include <stdlib.h>     //for exit()
int main()
{
    int fd[2];
    char buf[20];
    if(-1 == pipe(fd))
    {
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    write(fd[1], "hello,world", 12);
    memset(buf, '\0', sizeof(buf));
    read(fd[0], buf, 12);
    printf("The message is: %s\n", buf);
    return 0;
}

操作系统实验三

2、无名管道同步机制验证

修改代码,不写入数据时,不会进行读取:

write(fd[1], "", 0);

操作系统实验三

 

修改代码,写入过多数据时,无法再写入:

write(fd[1], "1234567890123456789012", 22);

操作系统实验三

 

同步机制:当写端存在时,管道中没有数据时,读取管道时将阻塞;当管道数据满时,此时再向管道写数据,写端将阻塞。

 

3、有名管道示例程序代码

fifo_snd.c:

/*
 *File: fifo_send.c
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <fcntl.h>


#define FIFO "/tmp/my_fifo"

int main()
{
    char buf[] = "hello,world";

    //`. check the fifo file existed or not
    int ret;
    ret = access(FIFO, F_OK);
    if(ret == 0)    //file /tmp/my_fifo existed
    {
        system("rm -rf /tmp/my_fifo");
    }

    //2. creat a fifo file
    if(-1 == mkfifo(FIFO, 0766))
    {
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }

    //3.Open the fifo file
    int fifo_fd;
    fifo_fd = open(FIFO, O_WRONLY);
    if(-1 == fifo_fd)
    {
        perror("open");
        exit(EXIT_FAILURE);

    }

    //4. write the fifo file
    int num = 0;
    num = write(fifo_fd, buf, sizeof(buf));
    if(num < sizeof(buf))
    {
        perror("write");
        exit(EXIT_FAILURE);
    }

    printf("write the message ok!\n");

    close(fifo_fd);

    return 0;
}

fifo_rcv.c:

/*
 *File: fifo_rcv.c
 */
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <fcntl.h>


#define FIFO "/tmp/my_fifo"

int main()
{
    char buf[20] ;
    memset(buf, '\0', sizeof(buf));

    //`. check the fifo file existed or not
    int ret;
    ret = access(FIFO, F_OK);
    if(ret != 0)    //file /tmp/my_fifo existed
    {
        fprintf(stderr, "FIFO %s does not existed", FIFO);
        exit(EXIT_FAILURE);
    }

    //2.Open the fifo file
    int fifo_fd;
    fifo_fd = open(FIFO, O_RDONLY);
    if(-1 == fifo_fd)
    {
        perror("open");
        exit(EXIT_FAILURE);

    }

    //4. read the fifo file
    int num = 0;
    num = read(fifo_fd, buf, sizeof(buf));

    printf("Read %d words: %s\n", num, buf);

    close(fifo_fd);

    return 0;
}

 

4、有名管道同步机制验证

先打开写端,还没打开读端时,写端会阻塞:

操作系统实验三

打开读之后成功通信:

操作系统实验三

操作系统实验三

先打开读端,后打开写端,同时阻塞。

操作系统实验三

操作系统实验三

 

同步机制:(参考https://blog.csdn.net/ly52352148/article/details/52302867

A.从FIFO中读取数据

约定:如果一个进程为了从FIFO中读取数据而以阻塞的方式打开FIFO, 则称内核为该进程的读操作设置了阻塞标志

<1>如果有进程为写而打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说返回-1,当前errno值为EAGAIN,提醒以后再试。

<2>对于设置阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其他进程正在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量。

<3>如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞

<4>如果写端关闭,管道中有数据读取管道中的数据,如果管道中没有数据读端将不会继续阻塞,此时返回0。

注意:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。

B.向FIFO中写入数据

约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作设置了阻塞标志。

对于设置了阻塞标志的写操作:

<1>当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳写入的字节数时,才开始进行一次性写操作。

<2>当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。

对于没有设置阻塞标志的写操作:

<1>当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。

<2>当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。

注意:只有读端存在,写端才有意义。如果读端不在,写端向FIFO写数据,内核将向对应的进程发送SIGPIPE信号(默认终止进程);

 

c)消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?

1、程序源代码

client
 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <signal.h>

#define BUF_SIZE 128

//Rebuild the strcut (must be)
struct msgbuf
{
    long mtype;
    char mtext[BUF_SIZE];
};


int main(int argc, char *argv[])
{
    //1. creat a mseg queue
    key_t key;
    int msgId;
    
    printf("THe process(%s),pid=%d started~\n", argv[0], getpid());

    key = ftok(".", 0xFF);
    msgId = msgget(key, IPC_CREAT|0644);
    if(-1 == msgId)
    {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    //2. creat a sub process, wait the server message
    pid_t pid;
    if(-1 == (pid = fork()))
    {
        perror("vfork");
        exit(EXIT_FAILURE);
    }

    //In child process
    if(0 == pid)
    {
        while(1)
        {
            alarm(0);
            alarm(100);     //if doesn't receive messge in 100s, timeout & exit
            struct msgbuf rcvBuf;
            memset(&rcvBuf, '\0', sizeof(struct msgbuf));
            msgrcv(msgId, &rcvBuf, BUF_SIZE, 2, 0);                
            printf("Server said: %s\n", rcvBuf.mtext);
        }
        
        exit(EXIT_SUCCESS);
    }

    else    //parent process
    {
        while(1)
        {
            usleep(100);
            struct msgbuf sndBuf;
            memset(&sndBuf, '\0', sizeof(sndBuf));
            char buf[BUF_SIZE] ;
            memset(buf, '\0', sizeof(buf));
            
            printf("\nInput snd mesg: ");
            scanf("%s", buf);
            
            strncpy(sndBuf.mtext, buf, strlen(buf)+1);
            sndBuf.mtype = 1;

            if(-1 == msgsnd(msgId, &sndBuf, strlen(buf)+1, 0))
            {
                perror("msgsnd");
                exit(EXIT_FAILURE);
            }
            
            //if scanf "end~", exit
            if(!strcmp("end~", buf))
                break;
        }
        
        printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());
    }

    return 0;
}

server

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <signal.h>

#define BUF_SIZE 128

//Rebuild the strcut (must be)
struct msgbuf
{
    long mtype;
    char mtext[BUF_SIZE];
};


int main(int argc, char *argv[])
{
    //1. creat a mseg queue
    key_t key;
    int msgId;
    
    key = ftok(".", 0xFF);
    msgId = msgget(key, IPC_CREAT|0644);
    if(-1 == msgId)
    {
        perror("msgget");
        exit(EXIT_FAILURE);
    }

    printf("Process (%s) is started, pid=%d\n", argv[0], getpid());

    while(1)
    {
        alarm(0);
        alarm(600);     //if doesn't receive messge in 600s, timeout & exit
        struct msgbuf rcvBuf;
        memset(&rcvBuf, '\0', sizeof(struct msgbuf));
        msgrcv(msgId, &rcvBuf, BUF_SIZE, 1, 0);                
        printf("Receive msg: %s\n", rcvBuf.mtext);
        
        struct msgbuf sndBuf;
        memset(&sndBuf, '\0', sizeof(sndBuf));

        strncpy((sndBuf.mtext), (rcvBuf.mtext), strlen(rcvBuf.mtext)+1);
        sndBuf.mtype = 2;

        if(-1 == msgsnd(msgId, &sndBuf, strlen(rcvBuf.mtext)+1, 0))
        {
            perror("msgsnd");
            exit(EXIT_FAILURE);
        }
            
        //if scanf "end~", exit
        if(!strcmp("end~", rcvBuf.mtext))
             break;
    }
        
    printf("THe process(%s),pid=%d exit~\n", argv[0], getpid());

    return 0;
}

 

2、验证同步机制

正常运行

操作系统实验三

操作系统实验三

当输入过长数据时:

操作系统实验三

 

同步机制:

当没有消息时,接收端阻塞;当消息队列满时,发送端阻塞。

如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行时若是消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码为ENOMSG。当msgflg为0时, msgsnd()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理模式。

 

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

参考:http://www.cnblogs.com/laiy/p/pintos_project1_thread.html

 

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函数,

/* Returns the number of timer ticks since the OS booted. */
int64_t
timer_ticks (void)
{
  enum intr_level old_level = intr_disable ();
  int64_t t = ticks;
  intr_set_level (old_level);
  return t;
}

timer_ticks函数中intr_disable函数获取了当前的中断状态, 然后将当前中断状态改为不能被中断,然后返回执行之前的中断状态。即禁止当前行为被中断,保存禁止被中断前的中断状态。

然后获取ticks的当前值返回。从pintos被启动开始,ticks就一直在计时,代表着操作系统执行单位时间的前进计量。

 

第8行循环中,timer_elapsed返回了当前时间距离then的时间间隔, 那么这个循环实质就是在ticks的时间内不断执行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);
}

其中第6行thread_current调用的running_thread, 把CPU栈的指针复制到esp中,然后调用pg_round_down返回当前线程起始指针

第9行断言这是个软中断, 第11和16行包裹起来的就是我们线程机制保证的一个原子性操作。第12-15行:当前线程不是空闲的线程就调用list_push_back把当前线程的元素扔到就绪队列里面,并把线程改成THREAD_READY状态

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的线程。

如果就绪队列空闲直接返回一个空闲线程指针,否则拿就绪队列第一个线程出来返回。

然后3个断言确保不能被中断,当前线程是RUNNING_THREAD等。

如果当前线程和下一个要跑的线程不是同一个的话调用switch_threads返回给prev。

这个函数实现是用汇编语言实现的在threads/switch.S里:

#### struct thread *switch_threads (struct thread *cur, struct thread *next);
####
#### Switches from CUR, which must be the running thread, to NEXT,
#### which must also be running switch_threads(), returning CUR in
#### NEXT's context.
####
#### This function works by assuming that the thread we're switching
#### into is also running switch_threads().  Thus, all it has to do is
#### preserve a few registers on the stack, then switch stacks and
#### restore the registers.  As part of switching stacks we record the
#### current stack pointer in CUR's thread structure.

.globl switch_threads
.func switch_threads
switch_threads:
    # Save caller's register state.
    #
    # Note that the SVR4 ABI allows us to destroy %eax, %ecx, %edx,
    # but requires us to preserve %ebx, %ebp, %esi, %edi.  See
    # [SysV-ABI-386] pages 3-11 and 3-12 for details.
    #
    # This stack frame must match the one set up by thread_create()
    # in size.
    pushl %ebx
    pushl %ebp
    pushl %esi
    pushl %edi

    # Get offsetof (struct thread, stack).
.globl thread_stack_ofs
    mov thread_stack_ofs, %edx

    # Save current stack pointer to old thread's stack, if any.
    movl SWITCH_CUR(%esp), %eax
    movl %esp, (%eax,%edx,1)

    # Restore stack pointer from new thread's stack.
    movl SWITCH_NEXT(%esp), %ecx
    movl (%ecx,%edx,1), %esp

    # Restore caller's register state.
    popl %edi
    popl %esi
    popl %ebp
    popl %ebx
        ret
.endfunc

先4个寄存器压栈保存寄存器状态(保护作用),然后全局变量thread_stack_ofs记录线程和棧之间的间隙。之后先把当前的线程指针放到eax中, 并把线程指针保存在相对基地址偏移量为edx的地址中。切换到下一个线程的线程棧指针,保存在ecx中,再把这个线程相对基地址偏移量edx地址(上一次保存现场的时候存放的)放到esp当中继续执行。

这里ecx, eax起容器的作用, edx指向当前现场保存的地址偏移量。

简单来说就是保存当前线程状态, 恢复新线程之前保存的线程状态。

然后再把4个寄存器拿出来,到现在eax(函数返回值是eax)就是被切换的线程棧指针。

我们由此得到一个结论,如果下一个线程和当前线程不一样的话,schedule先把当前线程丢到就绪队列,然后把线程切换

thread_schedule_tail

先是获得当前线程cur, 注意此时是已经切换过的线程了(或者还是之前run的线程, 因为ready队列为空)。然后把线程状态改成THREAD_RUNNING, 然后thread_ticks清零开始新的线程切换时间片。然后调用process_activate触发新的地址空间:1.更新页目录表 2.更新任务现场信息。

 

总结:

thread_schedule_tail:获取当前线程,分配恢复之前执行的状态和现场, 如果当前线程死了就清空资源。

schedule:拿下一个线程切换过来继续run。

thread_yield:把当前线程扔到就绪队列里,然后重新schedule,注意这里如果ready队列为空的话当前线程会继续在cpu执行。

timer_sleep:在ticks时间内,如果线程处于running状态就不断把他扔到就绪队列不让他执行。

 

github代码地址:https://github.com/Woochy-Young/OS/tree/master/LAB3

相关文章: