INT0x1Aとは
INT 0x1AはBIOSが提供する「今の時間や日付を教えてくれる仕組み」です。
この仕組みを使うと、アセンブリ言語から簡単に時計の情報を取得できます。
主な使い方
| AHレジスタの値 | できること |
|---|---|
| 02H | 今の「時・分・秒」を取得 |
| 04H | 今の「年・月・日」を取得 |
返り値の形式
時間や日付は BCD(Binary Coded Decimal) という形式で返されます。
1バイトで10進数2桁を表す形式
- 上位4ビット(ビット7〜4) → 10の位
- 下位4ビット(ビット3〜0) → 1の位
例:00110110
- 上位4ビット 0011 → 3
- 下位4ビット 0110 → 6
→ よって 36 となります
まずはINT0x1Aを使ってみる
INT 0x1Aがどんな動作をするのかを理解するため、実際に呼び出してみます。
test@test-fujitsu:~/kaihatsu/1a$ nasm -f bin boot.asm -o boot.bin
[org 0x7C00]
bits 16
start:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
sti
; VGA テキストモード
mov ah, 0
mov al, 3
int 0x10
; -------------------------------
; 時刻取得
mov ah, 2
int 0x1A ; CH=時間, CL=分, DH=秒
mov al, ch
call print_bcd2
mov al, cl
call print_bcd2
mov al, dh
call print_bcd2
; 改行
mov ah, 0x0E
mov al, 13
int 0x10
mov al, 10
int 0x10
; -------------------------------
; 日付取得
mov ah, 4
int 0x1A ; CH=世紀, CL=年(2025年ならCH=20,CL=25), DH=月, DL=日
mov al, ch
call print_bcd2
mov al, cl
call print_bcd2
mov al, dh
call print_bcd2
mov al, dl
call print_bcd2
hang:
hlt
jmp hang
; -------------------------------
; BCD (AL) → 2桁数字表示
; -------------------------------
print_bcd2:
push ax
mov ah, al
shr ah, 4
add ah, '0'
mov al, ah
call print_char
pop ax
and al, 0x0F
add al, '0'
call print_char
ret
; -------------------------------
; 1文字表示(AL)
; -------------------------------
print_char:
mov ah, 0x0E
int 0x10
ret
times 510-($-$$) db 0
dw 0xAA55
QEMU
test@test-fujitsu:~/kaihatsu/1a$ qemu-system-x86_64 -drive format=raw,file=boot.bin
ThinkPad X280
lsblk
sudo dd if=boot.bin of=/dev/sdb bs=512 count=1 conv=notrunc
sync
8086互換CPU(V30)
IBM BIOS上での実装
参考 BIOS ソース:
IBM-PC-BIOS/IBM PC AT/AT BIOS V3/
INT 1AHには複数の機能がありますが、ここでは 時間取得(AH=02H) と 日付取得(AH=04H) に絞って BIOS の実装を読み解きます。
以下は注釈から抜粋した仕様です。
実行すると、結果は複数のレジスタに格納されます。
※筆者は NASM を使用していますが、当時の BIOS は別のアセンブラで作られているため、文法が多少異なる場合があります。
; (AH) = 02H READ THE REAL TIME CLOCK AND RETURN WITH, :
; (CH) = HOURS IN BCD (00-23) :
; (CL) = MINUTES IN BCD (00-59) :
; (DH) = SECONDS IN BCD (00-59) :
; (DL) = DAYLIGHT SAVINGS ENABLE (00-01). :
; (AH) = 04H READ THE DATE FROM THE REAL TIME CLOCK AND RETURN WITH, :
; (CH) = CENTURY IN BCD (19 OR 20) :
; (CL) = YEAR IN BCD (00-99) :
; (DH) = MONTH IN BCD (01-12) :
; (DL) = DAY IN BCD (01-31). :
AHの機能番号が存在するかの確認処理
INT 1AH では、AH に入れた値によって実行する機能を切り替えます。
例:
AH = 02H → 時刻取得
AH = 04H → 日付取得
BIOS では、まず 指定されたAHが有効な番号か を確認します。
CMP AH,(RTC_TBE-RTC_TB)/2 ; CHECK IF COMMAND IN VALID RANGE (0-7)
CMC ; COMPLEMENT CARRY FOR ERROR EXIT
JC TIME_9 ; EXIT WITH CARRY = 1 IF NOT VALID
この処理を分解すると:
-
CMP op1, op2
op1 < op2 → CF=1
op1 >= op2 → CF=0 -
CMC
CFを反転します。
CF = 0 → CF = 1
CF = 1 → CF = 0 -
JC TIME_9
CF=1 の場合にTIME_9へ跳ぶ
つまりこの場合はAH が範囲外(8H以上)の場合にTIME_9(該当する機能番号無し)へ跳びます。
BIOS では、各 AH の値に対応する処理を 表 として登録しています。
表は DW(ワード=2バイト)で書かれているので、各項目は 2 バイトです。
RTC_TB DW RTC_00 ; 0 = READ CURRENT CLOCK COUNT
DW RTC_10 ; 1 = SET CLOCK COUNT
DW RTC_20 ; 2 = READ THE REAL TIME CLOCK TIME
DW RTC_30 ; 3 = SET REAL TIME CLOCK TIME
DW RTC_40 ; 4 = READ THE REAL TIME CLOCK DATE
DW RTC_50 ; 5 = SET REAL TIME CLOCK DATE
DW RTC_60 ; 6 = SET THE REAL TIME CLOCK ALARM
DW RTC_70 ; 7 = RESET ALARM
RTC_TBE EQU $
表の大きさ = RTC_TBE - RTC_TB(バイト数)
登録されている機能数 = サイズ ÷ 2
AHに登録された機能番号による分岐処理
AH の値が有効(1〜7)であることが確認できた後、BIOS は以下の処理で対応する関数を呼び出します。
SHR AX,8 ; CONVERT FUNCTION TO BYTE OFFSET
ADD AX,AX ; CONVERT FUNCTION TO WORD OFFSET (CY=0)
MOV SI,AX ; PLACE INTO ADDRESSING REGISTER
CLI ; NO INTERRUPTS DURING TIME FUNCTIONS
CALL CS:[SI]+OFFSET RTC_TB ; VECTOR TO FUNCTION REQUESTED WITH CY=0
; RETURN WITH CARRY FLAG SET FOR RESULT
SHR AX,8同AHの内容を右に8ビットずらしてALに格納します。
→ AX の上位バイト(AH)が AL 側に下りて、AH は 0 になります。
分かり易く言うとmov al, ah mov ah, 0と同義です。
ADD AX,AXはAXの値を2倍しています。
RTC_TBは各項目を2バイトで登録している為、オフセット位置は2の倍数になります。
例
- 0 : 0バイト目
- 1 : 2バイト目
- 2 : 4バイト目
- 3 : 6バイト目
CALL CS:[SI]+OFFSET RTC_TB
RTC_TBのアドレス + オフセットの位置にある関数を呼び出します。
例えばAH=04Hの場合、RTC_TB+オフセット8バイトの位置に登録されているRTC_TBが呼び出されます。
時間取得
RTC_20は実際に時間そのものを取得しているわけではなく、CMOSから値を読み込む CMOS_READを呼び出しています。
RTC_20: ; GET RTC TIME
CALL UPD_IPR ; CHECK FOR UPDATE IN PROCESS
JC RTC_29 ; EXIT IF ERROR (CY= 1)
MOV AL,CMOS_SECONDS ; SET ADDRESS OF SECONDS
CALL CMOS_READ ; GET SECONDS
MOV DH,AL ; SAVE
MOV AL,CMOS_REG_B ; ADDRESS ALARM REGISTER
CALL CMOS_READ ; READ CURRENT VALUE OF DSE BIT
AND AL,00000001B ; MASK FOR VALID DSE BIT
MOV DL,AL ; SET [DL] TO ZERO FOR NO DSE BIT
MOV AL,CMOS_MINUTES ; SET ADDRESS OF MINUTES
CALL CMOS_READ ; GET MINUTES
MOV CL,AL ; SAVE
MOV AL,CMOS_HOURS ; SET ADDRESS OF HOURS
CALL CMOS_READ ; GET HOURS
MOV CH,AL ; SAVE
CLC ; SET CY= 0
日時取得
RTC_40も同様にCMOS_READを使って日時を取得します。
RTC_40: ; GET RTC DATE
CALL UPD_IPR ; CHECK FOR UPDATE IN PROCESS
JC RTC_49 ; EXIT IF ERROR (CY= 1)
MOV AL,CMOS_DAY_MONTH ; ADDRESS DAY OF MONTH
CALL CMOS_READ ; READ DAY OF MONTH
MOV DL,AL ; SAVE
MOV AL,CMOS_MONTH ; ADDRESS MONTH
CALL CMOS_READ ; READ MONTH
MOV DH,AL ; SAVE
MOV AL,CMOS_YEAR ; ADDRESS YEAR
CALL CMOS_READ ; READ YEAR
MOV CL,AL ; SAVE
MOV AL,CMOS_CENTURY ; ADDRESS CENTURY LOCATION
CALL CMOS_READ ; GET CENTURY BYTE
MOV CH,AL ; SAVE
CLC ; SET CY=0
CMOS_READ
実際にCMOSから日時及び時間を呼び出す処理を知りたかったらCMOS_READの中身を読む必要があります。
ALに設定する値。今回呼び出すもののみ抜粋。呼び出し順に載せたので前後しています。
;---------- CMOS TABLE LOCATION ADDRESS'S ## -----------------------------------
CMOS_SECONDS EQU 000H ; SECONDS
CMOS_REG_B EQU 00BH ; STATUS REGISTER B ALARM
CMOS_MINUTES EQU 002H ; MINUTES
CMOS_HOURS EQU 004H ; HOURS
CMOS_DAY_MONTH EQU 007H ; DAY OF THE MONTH
CMOS_MONTH EQU 008H ; MONTH
CMOS_YEAR EQU 009H ; YEAR (TWO DIGITS)
CMOS_CENTURY EQU 032H ; DATE CENTURY BYTE (BCD)
;----------------------------------------
; CMOS EQUATES FOR THIS SYSTEM :
;-------------------------------------------------------------------------------
CMOS_PORT EQU 070H ; I/O ADDRESS OF CMOS ADDRESS PORT
CMOS_DATA EQU 071H ; I/O ADDRESS OF CMOS DATA PORT
;--- CMOS_READ -----------------------------------------------------------------
; READ BYTE FROM CMOS_SYSTEM CLOCK CONFIGURATION TABLE :
; :
; INPUT: (AL)= CMOS_TABLE ADDRESS TO BE READ :
; BIT 7 = 0 FOR NMI ENABLED AND 1 FOR NMI DISABLED ON EXIT :
; BITS 6-0 = ADDRESS OF TABLE LOCATION TO READ :
; :
; OUTPUT: (AL) VALUE AT LOCATION (AL) MOVED INTO (AL). IF BIT 7 OF (AL) WAS :
; ON THEN NMI LEFT DISABLED, DURING THE CMOS READ BOTH NMI AND :
; NORMAL INTERRUPTS ARE DISABLED TO PROTECT CMOS DATA INTEGRITY. :
; THE CMOS ADDRESS REGISTER IS POINTED TO A DEFAULT VALUE AND :
; THE INTERRUPT FLAG RESTORED TO THE ENTRY STATE ON RETURN. :
; ONLY THE (AL) REGISTER AND THE NMI STATE IS CHANGED. :
;-------------------------------------------------------------------------------
CMOS_READ PROC NEAR ; READ LOCATION (AL) INTO (AL)
PUSHF ; SAVE INTERRUPT ENABLE STATUS AND FLAGS
ROL AL,1 ; MOVE NMI BIT TO LOW POSITION
STC ; FORCE NMI BIT ON IN CARRY FLAG
RCR AL,1 ; HIGH BIT ON TO DISABLE NMI - OLD IN CY
CLI ; DISABLE INTERRUPTS
OUT CMOS_PORT,AL ; ADDRESS LOCATION AND DISABLE NMI
NOP ; I/O DELAY
IN AL,CMOS_DATA ; READ THE REQUESTED CMOS LOCATION
PUSH AX ; SAVE (AH) REGISTER VALUE AND CMOS BYTE
MOV AL,CMOS_REG_D*2 ; GET ADDRESS OF DEFAULT LOCATION
RCR AL,1 ; PUT ORIGINAL NMI MASK BIT INTO ADDRESS
OUT CMOS_PORT,AL ; SET DEFAULT TO READ ONLY REGISTER
POP AX ; RESTORE (AH) AND (AL), CMOS BYTE
PUSH CS ; *PLACE CODE SEGMENT IN STACK AND
CALL CMOS_POPF ; *HANDLE POPF FOR B- LEVEL 80286
RET ; RETURN WITH FLAGS RESTORED
CMOS_READ ENDP
ALの構成
AL = 7 6 5 4 3 2 1 0
| NMI | CMOS_ADDR |
ビット7: NMI 制御(0 = NMI有効、1 = NMI無効)
ビット6-0: CMOS のアドレス
今回使用するALの設定値は以下の通り
| ラベル | CMOS アドレス (16進) | AL (ビット列, NMI=0) | 説明 |
|---|---|---|---|
CMOS_SECONDS |
00H |
00000000 |
現在の秒 |
CMOS_MINUTES |
02H |
00000010 |
現在の分 |
CMOS_HOURS |
04H |
00000100 |
現在の時 |
CMOS_DAY_MONTH |
07H |
00000111 |
現在の日 |
CMOS_MONTH |
08H |
00001000 |
現在の月 |
CMOS_YEAR |
09H |
00001001 |
現在の年下2桁 |
CMOS_CENTURY |
32H |
00110010 |
世紀 (BCD) |
NMI情報をCFへ入れる
CMOS_CENTURY EQU 032Hを例とします。
ビット列にすると0 0 1 1 0 0 1 0となります。
- 操作1:
ROL AL,1
ビット列を左に1ビット回転させます。
この時ビット7(NMI)は 回転してビット0へ入ります。
AL 元: 0 0 1 1 0 0 1 0
ROL 1: 0 1 1 0 0 1 0 0
-
操作2:
STC
CARRYを1にする -
操作3:
RCR AL,1
ビット列を右に1ビット回転させます。
するとALは元に戻り、右端のビット0(元々のNMI)はCFに入ります。
AL 元: 0 1 1 0 0 1 0 0
RCR 1: 0 0 1 1 0 0 1 0
呼び出し後に戻すためにCFへ入れていますが、今回はビット7が全て0でありNMIは有効のままなのでこの処理は必要ありません。
CMOS から値を読み出す処理
実際にCMOSとやりとりをしているのは以下の2行です。
NOPは数クロック待機しCMOSの処理が終わるのを待つために置いてあります。
結果はBCD形式でALに格納されます。
OUT 070H,AL
NOP
IN AL,071H
2025年 5月 18日 17時 45分 36秒の例
| ラベル | CMOS アドレス (16進) | AL (ビット列, NMI=0) | 説明 | CMOS データ読み出し例 (IN 71H → AL) |
|---|---|---|---|---|
CMOS_SECONDS |
00H | 00000000 |
現在の秒 |
00110110 (BCD 36 → 36 秒) |
CMOS_MINUTES |
02H | 00000010 |
現在の分 |
01000101 (BCD 45 → 45 分) |
CMOS_HOURS |
04H | 00000100 |
現在の時 |
00010111 (BCD 17 → 17 時) |
CMOS_DAY_MONTH |
07H | 00000111 |
現在の日 |
00010010 (BCD 18 → 18 日) |
CMOS_MONTH |
08H | 00001000 |
現在の月 |
00000101 (BCD 05 → 5 月) |
CMOS_YEAR |
09H | 00001001 |
現在の年下2桁 |
00100101 (BCD 25 → 25 年) |
CMOS_CENTURY |
32H | 00110010 |
世紀 (BCD) |
00100000 (BCD 20 → 2000 年台) |
NMIの設定を戻す
今回、ALレジスタに設定した CMOS_SECONDS や CMOS_MINUTES などは ビット7が0 となっており、NMI は有効のままです。
もし仮にビット7が 1 の値を ALに設定して OUT 070H,ALを実行すると、NMI が無効になり、後続の割り込み処理に影響が出る可能性があります。
CMOS_READ では NMI の設定を復元する処理も用意されていますが、今回使用したラベルではビット7=0 なので、復元処理は 特に必要ありません。
BIOSを使わずに日時を取得
BIOSは安全や汎用性を考慮するため、コードが長くなりますが、簡単に言えばこの3行を使えばINT 1AHを使わずとも同じように日時及び時間が取得できます。
OUT 070H,AL
NOP
IN AL,071H
冒頭で実装したCMOS読み込みのソースを、INT 1AHを使わないように書き換えると以下のようになります。
[org 0x7C00]
bits 16
start:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
sti
; VGA テキストモード
mov ah, 0
mov al, 3
int 0x10
; -------------------------------
; 時刻取得
mov al,0x04 ; 時間
out 0x70,al
nop
in al,0x71
call print_bcd2
mov al,0x02 ; 分
out 0x70,al
nop
in al,0x71
call print_bcd2
mov al,0x00 ; 秒
out 0x70,al
nop
in al,0x71
call print_bcd2
; 改行
mov ah, 0x0E
mov al, 13
int 0x10
mov al, 10
int 0x10
; -------------------------------
; 日付取得
mov al,0x32 ; 世紀
out 0x70,al
nop
in al,0x71
call print_bcd2
mov al,0x09 ; 年
out 0x70,al
nop
in al,0x71
call print_bcd2
mov al,0x08 ; 月
out 0x70,al
nop
in al,0x71
call print_bcd2
mov al,0x07 ; 日
out 0x70,al
nop
in al,0x71
call print_bcd2
hang:
hlt
jmp hang
; -------------------------------
; BCD (AL) → 2桁数字表示
; -------------------------------
print_bcd2:
push ax
mov ah, al
shr ah, 4
add ah, '0'
mov al, ah
call print_char
pop ax
and al, 0x0F
add al, '0'
call print_char
ret
; -------------------------------
; 1文字表示(AL)
; -------------------------------
print_char:
mov ah, 0x0E
int 0x10
ret
times 510-($-$$) db 0
dw 0xAA55
QEMU
ThinkPad X280
8086互換CPU(V30)
最後に
今回、INT 0x1Aを使った時刻・日付の取得について解説しました。まとめると以下の通りです。
時刻取得は AH=02H、日付取得は AH=04H で呼び出します。
返り値は BCD 形式なので、表示する際には変換が必要です。
BIOS を経由しない場合でも、CMOS の I/O ポート 0x70/0x71 を直接操作すれば同じ値を取得できます。
今回参考にしたBIOSはかなり古いものですが、2018年発売のThinkPadから同じ方法で日時及び時間を取得できたことから、この仕組みは現代のパソコンにも残っていることが分かります。
CMOSを搭載していない8086復刻機で実験するとBIOSとOUT/INで表示される値に差が出ました。
例外処理の違いかもしれません。






