📰 newsreader

twostopbits score 0.06 好み 0.25 en

DOS: システムの稼働時間の取得 (movq.de)

原題: DOS: Determining System Uptime (movq.de)

dossystem uptimebiosinterruptsreal-time clockcmosfreedoswindows nt4
原文 ↗

日本語訳

# タイトル

DOS: システムの稼働時間を特定する (movq.de)

# 本文

blog - git - desktop - contact

2026-10-04

次回のAdvent of Codeに向けて、古いWindows、おそらくNT4を使うことをゆるく計画しています(最初は2000で行こうと思いましたが、私の古いマシンでは少し動作が重く、95/98は少し不安定です。NT4がちょうどいいバランスかもしれません)。そのOSを使うにあたって、私のツールボックスに足りなかったのは、Unixの`uptime`のようにシステムの稼働時間を表示する小さなプログラムでした。唯一知っている方法は、`net statistics server | more`を実行して、その結果を頭の中で計算することでしたが、それは少し面倒です。

そこで、`GetTickCount()`を単にラップしただけの小さなツールを作成しました。ついでにOS/2版も作りましたが、これも同様に`DosQuerySysInfo(QSV_MS_COUNT, ...)`を薄くラップしただけのものです。

では、DOSでもこれはできるのでしょうか?

このOSファミリーに関するわずかな経験から、これが単純な作業ではないことは分かっていましたし、HelpPCを調べても楽観的な気持ちにはなれませんでした。DOSは、この情報を明示的に保持していないように見えるからです。

目次:

NUL

(おそらく)最初に私が見つけたのは、Mark Aitchisonによる`UPTIME.PAS`でした。リポジトリ内の`.timestamps`ファイルから判断すると、このプログラムは1997年頃のものと思われます。

では、これは何をしているのでしょうか? 核となる部分は、単にこれだけです:

`assign(fyle, 'NUL');`

`GetFTime(fyle, t);`

私はPascalについてほとんど知識がない(30年前に少しDelphiを触った程度)ので、これら2行は「NUL」ファイルに対して何かを行っているのだと推測しました。C言語(OpenWatcom v2、`_dos_getftime()`)で書くと、以下のようになります:

```c

int main() {

int handle;

unsigned int r, date, time;

if ((r = _dos_open("NUL", O_RDONLY, &handle)) != 0) {

fprintf(stderr, "Could not open NUL: %u\n", r);

return 1;

}

_dos_getftime(handle, &date, &time);

_dos_close(handle);

printf("%04d-%02d-%02d %02d:%02d:%02d\n", YEAR(date), MONTH(date),

DAY(date), HOUR(time), MINUTE(time), SECOND(time));

return 0;

}

```

しかし、これでは現在の年月日と時刻が表示されるだけです。Aitchisonのプログラムは動作しましたが、私のプログラムは動作しませんでした。一体何が起きているのでしょうか?

「`Assign`はプログラムの内部管理データに対してのみ動作し、それ以外に直接的な影響を与えることはない……。単に内部管理データを操作するだけである。」

では……これは実際にファイルを開いているのでしょうか? おそらくそうではないのでは……?

Aitchisonのプログラムをデバッガ(デバッグシンボルはなく、キー入力を飲み込んだり重複させたりする非常に不安定なバージョンのTurbo Debuggerを使用)で実行した結果、ようやく以下の点にたどり着きました。

`INT 21,57`を呼び出していますが、BXレジスタがゼロに設定されています。

しかし、プログラムは依然として動作し、正しい稼働時間を表示します。ここでの私の結論は、これは`NUL`とは全く関係がなく、代わりに`stdin`(標準入力)の「更新日時」を読み取っているということです。

私はC言語のプログラムを、単に次のように書き換えました:

`_dos_getftime(0, &raw_date, &raw_time);`

これにより、同じ`INT 21,57`の呼び出しが行われ、今度は確かに稼働時間が表示されるようになりました。

このアプローチにはどのような副作用があるのでしょうか?

驚いたことに、DOSSHELL内から新しいコマンドプロンプトを開いても、正しい稼働時間が表示され続けました。しかし、`ctty`を使用すると動作が壊れます。

スクリーンショットが示しているのは以下の通りです:

`A:\uptime`を実行すると、稼働時間は3分37秒と表示されます。`ctty com1`を実行します(これはVMであり、ホストから`telnet localhost 3364`でシリアルコンソールに接続しています)。シリアルコンソールで再び`A:\uptime`を実行すると、今度は稼働時間がわずか5秒と表示されます。そして最後に再び`A:\uptime`を実行すると、今度はわずか2秒になります。つまり、`stdin`のタイムスタンプを再初期化し、このプログラムを壊してしまう操作が存在するのです。

また、DOSBoxでは動作せず、ゴミのような値が表示されます。

WindowsやOS/2でこのDOS版を実行した場合も同様に無意味な結果になりますが、幸いなことに、それらのシステムには専用のバージョンがあります。

もう一つ見つけたプログラムは、FreeDOSのリポジトリにあるJavier Gutierrez Chamorroによる`UPTIMEC`です。これは2017年のものです。

(これはPascal版に言及しており、Aitchisonのコードが`NUL`を使用しているとも主張しています。私の解釈が間違っているのか、それとも今まで誰も気づかなかったのでしょうか?)

ともあれ、Chamorroのバージョンは全く異なる仕組みで動作しており、DOSを(少なくとも時刻関数に関しては)バイパスしています。これはリアルタイムクロック(RTC)/ CMOSを直接使用しています。私の理解では、仕組みは以下の通りです:

したがって、最初の起動時には(「アラーム時刻」がまだ設定されていないと仮定すると)「稼働時間」はおそらくゼロになります。その後の実行では正しい結果が表示され、`ctty`のような操作によっても壊れることはありません。

参照:

このプログラムは、簡単に壊れることがないため、間違いなくこちらの方が優れています。ただし、少し設定が必要です。これは、私のDell Inspiron 6400で、起動直後およびシステムを電源オフした後に実行したものです:

稼働時間が2分と表示されていますが、これは正しくありません。プログラムはそれ以上のことは分かりません。単にCMOSメモリに保存されていた「アラーム時刻」を使用しただけであり、それらの値は再起動や電源オフの間も保持されます。したがって、このツールを正しく使うには、`autoexec.bat`内で`uptime /R`を実行するのが適切です。これにより、CMOSに保存されている値が現在時刻にリセットされ、その後コマンドラインから対話的に実行した際に正しい稼働時間が表示されるようになります。

ウェブで検索すると、次のような情報も見つかります:

考え方は、`INT 1A,0`を呼び出すことです。

そのStackOverflowの投稿には、以下のページへのリンクがあり、その一部が引用されています:

「2番目の問題は、BIOSの`int 0x1A`の動作方法によって発生します。システム時刻(現在のタイマティック値)を取得するためにこの関数を呼び出すたびに、現在のMIDNIGHTフラグも返され、かつそのフラグがリセットされます。しかし、BIOS関数はDOSの日付を更新しないため、次に日付を要求したときに正しく更新されません。DOSはこの動作を認識しているため、DOS関数を呼び出すときはMIDNIGHTフラグが正しく維持されます。もし自分でBIOSの`int 0x1A`を呼び出す場合は、必ずMIDNIGHTフラグの値をチェックし、もしセットされていたら元に戻さなければなりません。」

私はこれを軽く試しただけです。HelpPCの説明によると、これは真夜中からの経過秒数を返すとされており、私のテスト結果とも一致しました。StackOverflowの人物はこう述べています:

「もし誰も`INT 1A,1`を使ってこの時刻を設定していなければ、起動からの経過秒数が得られます。」

ふむ。誰か、あるいは何かが設定したのでしょうね。:-)

Chamorroのコードに戻ります。もし`autoexec.bat`で`uptime /R`を呼び出す必要があるのなら……そもそもなぜこのようなCMOSのハックを行う必要があるのでしょうか? 起動時に小さなuptimeプログラムにファイルを書き込ませ、そこに現在時刻を保存しておけばいいのではないでしょうか? そうすれば、その後の実行時に現在の日時とそのファイルを比較すれば済みます。

もちろん、それだとファイルシステムが散らかります。そして、おそらく最もコストのかかる解決策でしょう。しかし、おそらく最も堅牢で、最も移植性の高い解決策でもあるのではないでしょうか?

私のツールでは、以下の3つの理由から`_dos_getftime(0, ...)`のアプローチを採用しました:

1. 本当に時々これが必要なだけだからです。そもそも、厳密に「必要」というわけではなく、単なる好奇心です。

2. もしもっと良い、あるいは異なる方法を知っている人がいれば、教えてください。

原文(英語)を表示

blog - git - desktop - contact

2026-10-04

For next

Advent of Code,

I'm loosely planning to use some old Windows, probably

NT4

(I wanted to go with 2000 first, but that's a bit slow on my old box,

and 95/98 are a bit unstable; NT4 might hit the sweet spot?). What was

missing in my toolchest for that OS was a small program to print the

system's uptime -- just like uptime

on Unix. The only way I knew was to

do net statistics server | more

and then do the math in your head, but

that's a bit cumbersome.

So, I made

a little tool,

which is nothing more than just a thin wrapper around

GetTickCount()

.

While at it, I also made an OS/2 version, again just a thin wrapper,

this time around

DosQuerySysInfo(QSV_MS_COUNT, ...)

.

But can you also do this on DOS?

The little experience I had with this OS family told me that this wasn't going to be straightforward, and browsing through HelpPC didn't make me more optimistic. DOS simply doesn't appear to explicitly keep track of this information.

Table of contents:

NUL

(allegedly)The first thing I found was

UPTIME.PAS

by Mark Aitchison.

This program appears to be from 1997, judging by the .timestamps

file

in the repo.

So, what does it do? At the core, it's just this:

assign(fyle, 'NUL');

GetFTime(fyle, t);

Since I know very, very little about Pascal (only did some Delphi stuff 30 years ago), I assumed that these two lines meant:

NUL

,It would look something like this in C (OpenWatcom v2,

_dos_getftime()

):

int main() {

int handle;

unsigned int r, date, time;

if ((r = _dos_open("NUL", O_RDONLY, &handle)) != 0) {

fprintf(stderr, "Could not open NUL: %u\n", r);

return 1;

}

_dos_getftime(handle, &date, &time);

_dos_close(handle);

printf("%04d-%02d-%02d %02d:%02d:%02d\n", YEAR(date), MONTH(date),

DAY(date), HOUR(time), MINUTE(time), SECOND(time));

return 0;

}

But this would only ever print the current date and time for me. Aitchison's program worked, but mine didn't. What's going on?

Assign

only operates on the program's internal management data and has otherwise no immediate observable effect. [...] It merely operates on internal management data.

So ... does this actually open the file? Probably not ... ?

Running Aitchison's program in a debugger (with no debugging symbols and in a very glitchy version of Turbo Debugger, which would eat or duplicate key presses all the time -- argh) eventually got me to this point:

It does call INT 21,57, but the BX register is set to zero.

The program still works, though, and prints the correct uptime. My

conclusion here is that it has nothing to do with NUL

at all, but

instead reads the "modification time" of stdin.

I changed my C program to just do this:

_dos_getftime(0, &raw_date, &raw_time);

This results in the same INT 21,57 call and it indeed prints the uptime now:

What side effects does this approach have?

To my surprise, things like opening a new command prompt from within

DOSSHELL still shows the correct uptime. But what does break is using

ctty

:

Here's what that screenshot shows:

A:\uptime

prints an uptime of 3 minutes and 37 seconds.ctty com1

. (This is a VM and

I connect to that serial console with telnet localhost 3364

on my

host.)A:\uptime

in the serial console again now shows an uptime

of just 5 seconds.ctty con

and running

A:\uptime

one last time shows just 2 seconds now.So there are operations that reinitialize the timestamp of stdin and break this program.

It's also not working on DOSBox, where it just prints garbage:

Similar useless things happen when you run the DOS version on Windows or OS/2, but luckily we have dedicated versions for those systems:

Another program I found was

UPTIMEC

by Javier Gutierrez Chamorro,

also in the FreeDOS repo. It's from 2017.

(This one even references the Pascal version and it also claims that

Aitchison's code uses NUL

. Is my interpretation wrong or did nobody

notice this before?)

Anyway, Chamorro's version works completely different and bypasses DOS (at least regarding time functions): It uses the Real-Time Clock / CMOS directly. As I understand it, it goes like this:

So on the very first start, the "uptime" is probably zero -- assuming the

"alarm time" was unset so far. Subsequent runs will then show the

correct result, and things like ctty

won't break it.

See also:

This program is arguably better, because it can't break so easily, but it does require a little bit of setup. Here it is running on my Dell Inspiron 6400, right after boot and after the system was powered down:

It shows an uptime of 2 minutes, which isn't correct. The program

couldn't know better -- it just used whatever "alarm time" was stored in

CMOS memory,

and those values are kept between reboots and poweroffs. So the right

thing to do with this tool is to run it in autoexec.bat

as uptime

/R

: This resets the value stored in CMOS to the current time, and then

it will show the correct uptime on the next interactive run on the

command line.

Searching the web also reveals this:

The idea is to call INT 1A,0.

That StackOverflow post also links to this page and quotes a little section from it:

The second problem comes in because of how BIOS int 0x1A operates. Whenever you call this function to retrieve the system time (the current timer tick value) it also returns the current MIDNIGHT flag and RESETS THE FLAG. But since the BIOS function doesn't update the DOS date, the next time you ask for the date, it will not be updated correctly. DOS is aware of the behavior, so when you call any DOS function, the MIDNIGHT flag is maintained correctly. If you call BIOS int 0x1A yourself, you MUST check the MIDNIGHT flag value and turn it back on if it was set.

I only gave this a quick shot. According to the description on HelpPC, this returns the number of seconds since midnight, and that matches my tests. The person on StackOverflow said:

If nobody has set this time using

INT 1A,1

, you will get the seconds since boot.

Hm. I guess someone or something did set it then. :-)

Going back to Chamorro's code: If we need to call uptime /R

in

autoexec.bat

... why even bother with any of this CMOS hackery? Why not

have our little uptime program write a file during boot and store the

current time there? Then, subsequent runs will compare the current time

and date to that file?

Of course, this clutters the filesystem. And it's arguably the most expensive solution. But also probably the most solid one? And the most portable one?

In

my tool,

I went with the _dos_getftime(0, ...)

approach for three reasons:

I really only need this every now and then. And I don't really "need" it, anyway, I'm just curious.

If you know of more, better, different ways, let me know.

← 一覧に戻る