1. 程式人生 > 其它 >20191330雷清逸 學習筆記8

20191330雷清逸 學習筆記8

20191330 雷清逸 學習筆記8(第五章)

一、知識點歸納以及自己最有收穫的內容

知識點歸納

摘要

本章討論了定時器和定時器服務;介紹了硬體定時器的原理和基於Intel x86的 PC 中的硬體定時器;講解了 CPU操作和中斷處理;描述了Linux中與定時器相關的系統呼叫、庫函式和定時器服務命令;探討了程序間隔定時器、定時器生成的訊號,並通過示例演示了程序間隔定時器。程式設計專案的目的是要在一個多工處理系統中實現定時器、定時器中斷和間隔定時器。多工處理系統作為—個Linux程序執行,該系統是 Linux程序內併發任務的一個虛擬 CPU。Linux 程序的實時模式間隔定時器被設計為定期生成SIGALRM訊號,充當虛擬CPU的定時器中斷,虛擬CPU使用SIGALRM訊號捕捉器作為定時器的中斷處理程式。該專案可讓讀程序通過定時器佇列實現任務間隔定時器,還可讓讀程序使用Linux 訊號掩碼來實現臨界區,以防止各項任務和中斷處理程式之間出現競態條件。

最有收穫的部分

  • 有關硬體定時器的相關知識
  • 有關個人計算機定時器的相關知識
  • 有關CPU操作的知識
  • 有關中斷處理的知識
  • 有關時鐘服務函式的知識
  • 有關間隔定時器的知識

硬體定時器

  • 定時器是由時鐘源和可程式設計計數器組成的硬體裝置。時鐘源通常是一個晶體振盪器,會產生週期性電訊號,以精確的頻率驅動計數器。
  • 計數器週期稱為定時器刻度,是系統的基本計時單元。

個人計算機定時器

  • 1.實時時鐘(RTC):

    (1)RTC由一個小型備用電池供電。即使在個人計算機關機時,它也能繼續執行。它用於實時提供時間和日期資訊。

    (2)在所有類Unix系統中,時間變數是一個長整數,包含從1970年7月1日起經過的秒數。

  • 2.可程式設計間隔定時器(PIT)(Wang2015):

    (1)PIT是與CPU分離的一個硬體定時器。

    (2)可對它進行程式設計,以提供以毫秒為單位的定時器刻度。

  • 3.多核CPU中的本地定時器(Intel 1997;Wang 2015):

    (1)在多核CPU中,每個核都是一個獨立的處理器,它有自己的本地定時器,由CPU時鐘驅動。

4.高解析度定時器:

(1)大多數電腦都有一個時間戳定時器(TSC),由系統時鐘驅動。

CPU操作

  • 每個CPU都有一個程式計數器(PC),也稱為指令指標(IP),以及一個標誌或狀態暫存器(SR)、一個堆疊指標(SP)和幾個通用暫存器,當PC指向記憶體中要執行的下一條指令時,SR包含CPU的當前狀態,如操作模式、中斷掩碼和條件碼,SP指向當前堆疊棧頂。

  • 在以上各步驟中,由於無效地址、非法指令、越權等原因,可能會出現一個錯誤狀態,稱為異常或陷阱。

  • 中斷是I/O裝置或協處理器傳送給CPU的外部訊號,請求CPU服務。

  • 中斷處理和異常處理都在作業系統核心中進行。在大多數情況下,使用者級程式無權訪問它們,但它們是理解作業系統(如Linux)定時器服務和訊號的關鍵。

中斷處理

  • 外部裝置(如定時器)的中斷被饋送到中斷控制器的預定義輸入行(Intel 1990;Wang 2015),按優先順序對中斷輸入排序,並將具有最高優先順序的中斷作為中斷請求(IRQ)路由到CPU。
  • 對於每個中斷,可以程式設計中斷控制器以生成一個唯一編號,叫做中斷向量,標識中斷源。在獲取中斷向量號後,CPU用它作為記憶體中中斷向量表(AMD64 2011)中的條目索引,條目包含一個指向中斷處理程式入口地址的指標來實際處理中斷。

時鐘服務函式

時鐘服務可通過系統呼叫、庫函式和使用者級命令呼叫。

以下是Linux的一些基本時鐘服務函式:

  • gettimeofday-settimeofday
#include <sys/time.h>
int gettimeofday(struct timeval *tv,struct timezone *tz);
int settimeofday(const struct timeval *tv,const struct timezone *tz);

這是對Linux核心的系統呼叫;settimeofday()函式用於設定當前時間;它可以通過庫函式ctime(&time)轉化為日曆形式。

  • time系統呼叫
time_t time(time_t *t)

該函式會以為單位返回當前時間

time系統呼叫具有一定的侷限性,只提供以為單位的解析度,而不是以微秒為單位。

  • times系統呼叫
clock_t times(struct tms *buf)

該函式可用於獲取某程序的具體執行時間。它將程序時間儲存在struct tms buf中

  • time和date命令
    • date:列印或設定系統日期和時間。
    • time:報告程序在使用者模式下和系統模式下的執行時間和總時間。
    • hwclock:查詢並設定硬體時鐘(RTC),也可以通過BIOS來完成。

間隔定時器

Linux為每個程序提供了三種不同型別的間隔計時器。

間隔計時器由setitimer()系統呼叫建立。getitimer()系統呼叫返回間隔定時器的狀態。

  • 分類:

    (1)ITIMER_REAL:實時減少,在到期時生成一個SIGALRM(14)訊號。

    (2)ITIMER_VIRTUAL:僅當程序在使用者模式下執行時減少,在到期時生成一個SIGVTALRM(26)訊號。

    (3)ITIMER_PROF:當程序正在使用者模式和系統(核心)模式下執行時減少。

    函式getitimer()用當前值填充curr_value指向的結構體,即引數which(ITIMER_REAL、ITIMER_VIRTUAL或ITIMER_PROF三者之一)指定的定時器在下次到期之前剩餘的時間。

REAL模式間隔定時器

  • VIRTUAL和PROF模式下的間隔計時器僅在執行程序時才有效。這類定時器的資訊可儲存在各程序的PROC結構體中。
  • 在大多數作業系統核心中,使用的資料結構都是定時器佇列。

二、問題與解決思路

問題:Linux下的定時器有sleep,usleep,nanosleep等等,它們有什麼區別呢?

答:經過對相關資料的查閱,發現它們的區別如下:

sleep()和nanosleep()都是使程序睡眠一段時間後被喚醒,但是二者的實現完全不同。
Linux中並沒有提供系統呼叫sleep(),sleep()是在庫函式中實現的,它是通過呼叫alarm()來設定報警時間,呼叫sigsuspend()將程序掛起在訊號SIGALARM上,sleep()只能精確到秒級上。

nanosleep()則是Linux中的系統呼叫,它是使用定時器來實現的,該呼叫使呼叫程序睡眠,並往定時器佇列上加入一個timer_list型定時器,time_list結構裡包括喚醒時間以及喚醒後執行的函式,通過nanosleep()加入的定時器的執行函式僅僅完成喚醒當前程序的功能。系統通過一定的機制定時檢查這些佇列(比如通過系統呼叫陷入核心後,從核心返回使用者態前,要檢查當前程序的時間片是否已經耗盡,如果是則呼叫schedule()函式重新排程,該函式中就會檢查定時器佇列,另外慢中斷返回前也會做此檢查),如果定時時間已超過,則執行定時器指定的函式喚醒呼叫程序。當然,由於系統時間片可能丟失,所以nanosleep()精度也不是很高。

alarm()也是通過定時器實現的,但是其精度只精確到秒級,另外,它設定的定時器執行函式是在指定時間向當前程序傳送SIGALRM訊號。

三、實踐內容與截圖,程式碼連結

實踐內容為書本P162-163頁的示例5.1,示例5.2及示例5.3

  • 示例5.1(gettimeofday系統呼叫)
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

struct timeval t;

int main()
{
    gettimeofday(&t,NULL);
    printf("sec: %ld usec=%ld\n", t.tv_sec,t.tv_usec);
    printf((char *)ctime(&t.tv_sec));
}

實踐截圖如下:

  • 示例5.2(settimeofday系統呼叫)
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>

struct timeval t;
int main()
{
	int r;
	t.tv_sec = 123456789;
	t.tv_usec = 0;
	r = settimeofday(&t,NULL);
	if (!r){
		printf("settimeofday() failed\n");
		exit(1);
	}
	gettimeofday(&t,NULL);
	printf("sec=%ld usec=%ld\n",t.tv_sec,t.tv_usec);
	printf("%s",ctime(&t.tv_sec));
}

實踐截圖如下:

  • 示例5.3(time系統呼叫)
#include <stdio.h>
#include <time.h>

time_t start,end;

int main()
{
	int i;
	start = time(NULL);
	printf("start=%ld\n",start);
	for(i=0;i<123456789;i++)
	end = time(NULL);
	printf("end =%ld time=%ld\n",end,end-start);
}

實踐截圖如下: