目次: Linux
先日(2024年10月1日の日記参照)のUbuntu 22とxrdpの組み合わせで起きるエラーですが、Ubuntu 20で再現できるかやってみました。グラフィクスは種類が違いますがAMDのGPU(Radeon RX 6900 XT)です。条件は同様でgdm3は終了させてxrdpのみ起動しています。
結論から言うと再現しました。以下が通常時のログです。正常に画面が表示されます。
gnome-session-binary[2712263]: DEBUG(+): Enabling debugging gnome-session-binary[2712263]: GLib-DEBUG(+): posix_spawn avoided (fd close requested) gnome-session-binary[2712263]: GLib-GIO-DEBUG(+): _g_io_module_get_default: Found default implementation dconf (DConfSettingsBackend) for ‘gsettings-backend’ gnome-session-binary[2712263]: WARNING: Error creating FIFO: File exists
ログにはデバッグ有効であること以外は特に何も出ません。FIFOが作れない警告が出ます、これは何だろう?まあいいか。
次にcard0とrenderD128をリネームしてアクセスできないようにして試しました。画面は真っ黒になってすぐに切断されます。Ubuntu 20.04 LTSでも再現するんですね。ログは下記のようになりました。
gnome-session-binary[2712010]: DEBUG(+): Enabling debugging gnome-session-binary[2712010]: GLib-DEBUG(+): posix_spawn avoided (fd close requested) gnome-session-is-accelerated: No hardware 3D support. gnome-session-check-accelerated: GL Helper exited with code 256 amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) ** (gnome-session-check-accelerated-gles-helper:2712146): WARNING **: 14:44:50.573: eglInitialize() failed gnome-session-check-accelerated: GLES Helper exited with code 256 gnome-session-binary[2712010]: DEBUG(+): hardware acceleration check failed: Child process exited with code 1 gnome-session-binary[2712010]: GLib-DEBUG(+): setenv()/putenv() are not thread-safe and should not be used after threads are created gnome-session-binary[2712010]: GLib-DEBUG(+): posix_spawn avoided (fd close requested) gnome-session-is-accelerated: No hardware 3D support. gnome-session-check-accelerated: GL Helper exited with code 256 amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) ** (gnome-session-check-accelerated-gles-helper:2712203): WARNING **: 14:44:55.610: eglInitialize() failed gnome-session-check-accelerated: GLES Helper exited with code 256 gnome-session-binary[2712010]: WARNING: software acceleration check failed: Child process exited with code 1 gnome-session-binary[2712010]: CRITICAL: We failed, but the fail whale is dead. Sorry....
前回も見かけたamdgpu_get_auth()のエラーが出て止まりました。AMDのGPUの場合、エラーが起きると止まってしまう疑いが強まりました。本当はエラーが起きたらソフトウェアレンダリングにフォールバックしてほしいんですけど、そうならないようです……。
デバイスファイルが/dev/driに全く存在しなくてもamdgpu_get_auth()が怒ってくるところを見ると、X.orgはPCのグラフィクスの種類(VirtualBoxなのかAMDのGPUなのか)を最初から決め打ちにしているんですね。
 この記事にコメントする
 この記事にコメントする
目次: Linux
昨日(2024年10月1日の日記参照)のUbuntu 22とxrdpの組み合わせで起きるエラーですが、VirtualBox 7.0.18で再現できるかやってみました。グラフィクスはVMSVGAを選択しています。VMSVGAは/dev/driの下にcard0とrenderD128が生成されるようです。ちなみにgdm3は終了させてxrdpのみ起動しています。
ちなみに結論から言っておくと再現しません。以下が通常時のログです。正常に画面が表示されます。
gnome-session-binary[2774]: DEBUG(+): Enabling debugging gnome-session-binary[2774]: GLib-GIO-DEBUG(+): _g_io_module_get_default: Found default implementation dconf (DConfSettingsBackend) for ‘gsettings-backend’
ログにはデバッグ有効であることと、libEGLが何かしら見つけて反応しています。それ以外は特に何も出ません。
次にcard0とrenderD128をリネームしてアクセスできないようにして試しました。しかし正常に画面が表示されます。えぇー?ログは下記のようになりました。
gnome-session-binary[4319]: DEBUG(+): Enabling debugging gnome-session-check-accelerated: GL Helper exited with code 512 libEGL warning: DRI2: failed to authenticate gnome-session-check-accelerated: GLES Helper exited with code 512 gnome-session-binary[4319]: GLib-GIO-DEBUG(+): _g_io_module_get_default: Found default implementation dconf (DConfSettingsBackend) for ‘gsettings-backend’
AMDのGPUでも見かけたfailed to authenticateが出ています。が、エラーは無視されるんでしょうか?謎の動きですね。
 この記事にコメントする
 この記事にコメントする
目次: Linux
WindowsからUbuntu 22.04 LTSのxrdpに接続すると真っ黒な画面しか出ず、すぐに接続が切断されてしまう問題に遭遇しました。どこでクラッシュしているか調べるため、xrdpがGNOMEを起動するところまでを追いかけると下記のようになっていました。
/etc/xrdp/startwm.sh
  exec /bin/sh /etc/X11/Xsession
    /etc/X11/Xsession.d/99x11-common_start
      /usr/bin/ssh-agent /usr/bin/im-launch x-session-manager
        /usr/bin/x-session-manager
          /usr/libexec/gnome-session-binary
スクリプトx-session-managerを書き換えてgnome-session-binaryの引数に--debugを足すと、GNOMEはより詳細なメッセージを出力します。メッセージはホームディレクトリの.xsession-errorsに記録されます。GNOMEのエラーメッセージは下記です。
** (gnome-session-check-accelerated-gles-helper:3301100): WARNING **: 13:50:04.884: eglInitialize() failed gnome-session-check-accelerated: GLES Helper exited with code 256 gnome-session-binary[3300977]: DEBUG(+): hardware acceleration check failed: Child process exited with code 1 gnome-session-binary[3300977]: GLib-DEBUG(+): setenv()/putenv() are not thread-safe and should not be used after threads are created gnome-session-binary[3300977]: GLib-DEBUG(+): posix_spawn avoided (fd close requested) gnome-session-is-accelerated: No hardware 3D support. gnome-session-check-accelerated: GL Helper exited with code 256 ★★ここでエラーが発生している amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) amdgpu_device_initialize: amdgpu_get_auth (1) failed (-1) ** (gnome-session-check-accelerated-gles-helper:3301174): WARNING **: 13:50:09.927: eglInitialize() failed gnome-session-check-accelerated: GLES Helper exited with code 256 gnome-session-binary[3300977]: WARNING: software acceleration check failed: Child process exited with code 1 gnome-session-binary[3300977]: CRITICAL: We failed, but the fail whale is dead. Sorry....
エラーメッセージ曰くamdgpu_get_auth()が失敗しています。関数名から推測するにAMDのGPUが装着されているマシンでしか発生しないエラーなのかもしれません。この関数は何者なのかDRMのソースコード(Mesa/libdrmのソースコードリポジトリへのリンク)を見ると、
// drm/amdgpu/amdgpu_device.c
/**
* Get the authenticated form fd,
*
* \param   fd   - \c [in]  File descriptor for AMD GPU device
* \param   auth - \c [out] Pointer to output the fd is authenticated or not
*                          A render node fd, output auth = 0
*                          A legacy fd, get the authenticated for compatibility root
*
* \return   0 on success\n
*          >0 - AMD specific error code\n
*          <0 - Negative POSIX Error code
*/
static int amdgpu_get_auth(int fd, int *auth)
{
	int r = 0;
	drm_client_t client = {};
	if (drmGetNodeTypeFromFd(fd) == DRM_NODE_RENDER)
		*auth = 0;
	else {
		client.idx = 0;
		r = drmIoctl(fd, DRM_IOCTL_GET_CLIENT, &client);    //★★エラーを返すのはこの関数だけ★★
		if (!r)
			*auth = client.auth;
	}
	return r;
}
// drm/xf86drm.c
/**
 * Call ioctl, restarting if it is interrupted
 */
drm_public int
drmIoctl(int fd, unsigned long request, void *arg)
{
    int ret;
    do {
        ret = ioctl(fd, request, arg);    //★★drmIoctl()はioctl()が返すエラーをそのまま返す★★
    } while (ret == -1 && (errno == EINTR || errno == EAGAIN));
    return ret;
}
ソースコードから推測するにグラフィクス系のデバイスファイルにioctl()しようとして失敗しているものと思われます。
XのグラフィックスはDRI(Direct Rendering Infrastructure)と呼ばれるインタフェースを経由してGPUを使用します。Xとカーネルの間にはlibDRMがおり、libDRMはLinuxカーネルのDRM(Direct Rendering Manager)を使用してGPUにアクセスします。DRMのデバイスファイルは/dev/driディレクトリの下にありますが、名前がDRIだったりDRMだったりして謎ですね。グラフィクス周りはあまり詳しくなくて理由はわかりません……。
# ls /dev/dri/* -la crw-rw-rw-+ 1 root video 226, 0 4月 30 15:00 /dev/dri/card0 crw-rw-rw-+ 1 root render 226, 128 4月 30 15:00 /dev/dri/renderD128
上記のようにキャラクタデバイス/dev/dri/card0と/dev/dri/renderD128のパーミッションを0666にしたところエラーが出なくなりました。今回はこの2つのデバイスファイルで解決しましたが、他のデバイスファイルにもアクセスしていた場合はさらに調査が必要かもしれません。グラフィクス系のエラーは簡単に調べる方法がないのが辛いですね。
ちなみにこの症状はQEMUだと再現しません。card0のパーミッションを0000にするとlibEGLがエラーになるのですが、なぜかGNOMEはエラーを無視して起動してしまいます。AMDのときだけなんで止まるんでしょうか。いまいち釈然としません……。
 この記事にコメントする
 この記事にコメントする
クラッキングでぶっ壊されてしまったニコニコ動画が復活し、シン・ニコニコ動画になって帰ってきました。いちユーザーとしては嬉しい気分でいっぱいです。
しかし前と比べて困ったことが起きていて、BizHawkで出力した動画をシン・ニコニコ動画にアップロードしようとすると「この動画の映像形式には対応していません」とエラーが出てしまって投稿できません。試行錯誤の末、解決方法を見つけたのでメモしておきます。
端的に言えば原因はピクセルフォーマットでした。BizHawkは特に何も言わないとYUV444で出力しますが、ニコニコ動画はYUV420でないと受け付けてくれないようです。どこにもそんな制約は書いていないため、仕様か?バグか?どちらともわかりません。
YUV420にする方法ですけども、BizHawkの出力設定を[Custom]にして、下記のようにピクセルフォーマットを明示的に指定すればYUV420で出力してくれました。
-c:a aac -c:v libx264 -preset ultrafast -pix_fmt yuv420p -f mp4
もしすでにYUV444で出力済みの動画ならば、ffmpegを使って再エンコードすれば良いです。
$ ffmpeg -i input.mp4 \
    -vcodec libx264 -vb 2048000 -r 60 -s 1280x720 -pix_fmt yuv420p \
    -acodec aac -ar 44100 -ab 192000 output.mp4
動画ビットレート(-vb)、フレームレート(-r)、動画サイズ(-s)は元の動画に応じて調整してください。
 この記事にコメントする
 この記事にコメントする
目次: Linux
以前、OpenSBIがRISC-V CPUの拡張機能をどのように認識し有効にするか調べました。今回はどのようにCPUの数を認識するか調べます。OpenSBIのプラットフォームは今まで同様にgenericを使います。
実はそんなに難しくなく、デバイスツリーの/cpuノードを読みに行くだけのようです。/cpuノードの例としてQEMU virtマシン、4CPUで起動したときにQEMUが生成するデバイスツリーを下記に示します。長いのでCPU_2, 3は省略しています。名前やラベル、regが2や3になるだけです。
/ {
	cpus {
		#address-cells = <1>;
		#size-cells = <0>;
		timebase-frequency = <10000000>;
		cpu0: cpu@0 {
			compatible = "riscv";
			reg = <0>;
			device_type = "cpu";
			riscv,cbop-block-size = <64>;
			riscv,cboz-block-size = <64>;
			riscv,cbom-block-size = <64>;
			riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "h",
				"zic64b", "zicbom", "zicbop", "zicboz",
				"ziccamoa", "ziccif", "zicclsm", "ziccrse",
				"zicntr", "zicsr", "zifencei", "zihintntl",
				"zihintpause", "zihpm", "za64rs", "zawrs",
				"zfa", "zca", "zcd", "zba", "zbb", "zbc", "zbs",
				"ssccptr", "sscounterenw", "sstc", "sstvala",
				"sstvecd", "svadu";
			riscv,isa-base = "rv64i";
			riscv,isa = "rv64imafdch_zic64b_zicbom_zicbop_zicboz_ziccamoa_ziccif_zicclsm_ziccrse_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_za64rs_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_ssccptr_sscounterenw_sstc_sstvala_sstvecd_svadu";
			mmu-type = "riscv,sv57";
			status = "okay";
			cpu0_intc: interrupt-controller {
				compatible = "riscv,cpu-intc";
				interrupt-controller;
				#interrupt-cells = <1>;
			};
		};
		cpu1: cpu@1 {
			compatible = "riscv";
			reg = <1>;
			device_type = "cpu";
			riscv,cbop-block-size = <64>;
			riscv,cboz-block-size = <64>;
			riscv,cbom-block-size = <64>;
			riscv,isa-extensions = "i", "m", "a", "f", "d", "c", "h",
				"zic64b", "zicbom", "zicbop", "zicboz",
				"ziccamoa", "ziccif", "zicclsm", "ziccrse",
				"zicntr", "zicsr", "zifencei", "zihintntl",
				"zihintpause", "zihpm", "za64rs", "zawrs",
				"zfa", "zca", "zcd", "zba", "zbb", "zbc", "zbs",
				"ssccptr", "sscounterenw", "sstc", "sstvala",
				"sstvecd", "svadu";
			riscv,isa-base = "rv64i";
			riscv,isa = "rv64imafdch_zic64b_zicbom_zicbop_zicboz_ziccamoa_ziccif_zicclsm_ziccrse_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_za64rs_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_ssccptr_sscounterenw_sstc_sstvala_sstvecd_svadu";
			mmu-type = "riscv,sv57";
			status = "okay";
			cpu1_intc: interrupt-controller {
				compatible = "riscv,cpu-intc";
				interrupt-controller;
				#interrupt-cells = <1>;
			};
		};
		cpu2: cpu@2 {
			/* 省略 */
		};
		cpu3: cpu@3 {
			/* 省略 */
		};
OpenSBIがCPU数を確認するコードは下記のようになっています。
// opensbi/platform/generic/platform.c
unsigned long fw_platform_init(unsigned long arg0, unsigned long arg1,
				unsigned long arg2, unsigned long arg3,
				unsigned long arg4)
{
	const char *model;
	void *fdt = (void *)arg1;    //★★デバイスツリーのアドレス、レジスタa1を使ってOpenSBIに渡す★★
	u32 hartid, hart_count = 0;
	int rc, root_offset, cpus_offset, cpu_offset, len;
	//...
	cpus_offset = fdt_path_offset(fdt, "/cpus");
	if (cpus_offset < 0)
		goto fail;
	fdt_for_each_subnode(cpu_offset, fdt, cpus_offset) {
		//★★device_typeがcpu、regプロパティに値が入っていれば、CPUのノードとみなす★★
		rc = fdt_parse_hart_id(fdt, cpu_offset, &hartid);
		if (rc)
			continue;
		if (SBI_HARTMASK_MAX_BITS <= hartid)
			continue;
		//★★statusプロパティがokかokayなら有効なCPUとみなす★★
		if (!fdt_node_is_enabled(fdt, cpu_offset))
			continue;
		//★★ハート数を1つ増やす★★
		//★★hartidは連番とは限らないため、hartindexとhartidの対応表を作る★★
		generic_hart_index2id[hart_count++] = hartid;
	}
	//★★platform領域に記録する★★
	platform.hart_count = hart_count;
	platform.heap_size = fw_platform_calculate_heap_size(hart_count);
	platform_has_mlevel_imsic = fdt_check_imsic_mlevel(fdt);
デバイスツリー(正確にはFDT: flattened device tree)の/cpuノードを見て、子ノードがCPUの定義であり、有効ならばhart_countを+1するシンプルなコードです。hartidは連番とは限らないため、hartindex(0からCPU数 - 1までの連番)とhartidの対応表generic_hart_index2id[]も同時に作ります。
無効なCPUの扱いも見ておきます。先程のコードからわかる通りstatus = "disabled"つまり無効なCPUの場合は、hartindexとhartidの対応表(platform.hart_index2id[])にhartidが載りません。例としてQEMUにてCPU 4つで起動し、デバイスツリーでCPU 2だけstatus = "disabled"にしたときのhartindexとhartidの対応表をダンプします。
(gdb) p platform
$11 = {opensbi_version = 65541, platform_version = 1,
  name = "riscv-virtio,qemo", '\000' <repeats 46 times>, features = 2,
  hart_count = 3, hart_stack_size = 8192, heap_size = 39936, reserved = 0,
  platform_ops_addr = 2148010200, firmware_context = 0,
  hart_index2id = 0x80083700 <generic_hart_index2id>}
(gdb) p generic_hart_index2id
$15 = {0, 1, 3, 0 <repeats 125 times>}
全CPUが有効ならば0, 1, 2, 3となりますが、CPU 2が無効なので0, 1, 3となっていることがわかります。
次にメインCPUがサブCPUを起こしに行くコードを見ます。アセンブラなので若干分かりづらいですが、hartindexとhartidの対応表に自CPUのhartidが登録されていればCPUを起動し、登録されていない場合はCPUを起動しません。
// opensbi/firmware/fw_base.S
_fdt_reloc_done:
	//★★メインCPUはこちら★★
	//★★初期化処理が終わるとここに到達する★★
	//★★_boot_statusにBOOT_STATUS_BOOT_HART_DONEを書き込むとサブCPU側がループを抜ける★★
	/* mark boot hart done */
	li	t0, BOOT_STATUS_BOOT_HART_DONE
	lla	t1, _boot_status
	fence	rw, rw
	REG_S	t0, 0(t1)
	j	_start_warm
	//★★サブCPUはこちら★★
	//★★_boot_statusを読みながらループで待っている★★
	/* waiting for boot hart to be done (_boot_status == 2) */
_wait_for_boot_hart:
	li	t0, BOOT_STATUS_BOOT_HART_DONE
	lla	t1, _boot_status
	REG_L	t1, 0(t1)
	/* Reduce the bus traffic so that boot hart may proceed faster */
	div	t2, t2, zero
	div	t2, t2, zero
	div	t2, t2, zero
	bne	t0, t1, _wait_for_boot_hart
_start_warm:
	/* Reset all registers except ra, a0, a1, a2, a3 and a4 for non-boot HART */
	li	ra, 0
	call	_reset_regs
	/* Disable all interrupts */
	csrw	CSR_MIE, zero
	//★★platform.hart_index2idを見て、hartidと一致する要素があるか確かめる★★
	/* Find HART count and HART stack size */
	lla	a4, platform
#if __riscv_xlen > 32
	lwu	s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4)
	lwu	s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4)
#else
	lw	s7, SBI_PLATFORM_HART_COUNT_OFFSET(a4)
	lw	s8, SBI_PLATFORM_HART_STACK_SIZE_OFFSET(a4)
#endif
	//★★s9はplatform.hart_index2id[0]のアドレス★★
	REG_L	s9, SBI_PLATFORM_HART_INDEX2ID_OFFSET(a4)
	/* Find HART id */
	csrr	s6, CSR_MHARTID
	//★★platform.hart_index2idがNULLだったら処理を止める★★
	/* Find HART index */
	beqz	s9, 3f
	li	a4, 0
	//★★a4 = hartindex
	//★★a5 = platform.hart_index2id[hartindex]の値(hartindexに対応するhartid)
	//★★s6 = CPUのhartid
	//★★s7 = hart数
	//★★s9 = platform.hart_index2id[hartindex]のアドレス
1:
#if __riscv_xlen > 32
	lwu	a5, (s9)
#else
	lw	a5, (s9)
#endif
	//★★hartidとplatform.hart_index2id[n]が一致していたら、ループを抜ける★★
	beq	a5, s6, 2f
	//★★s9を進めて、platform.hart_index2id[0], [1], [2], ...を順に調べる★★
	add	s9, s9, 4
	add	a4, a4, 1
	blt	a4, s7, 1b
	//★★s6 = hartindex
2:	add	s6, a4, zero
	//★★hartindexがhart数を超えているか?
	//★★  超える  : CPUが無効である、サブCPUを起動しない -> _start_hangへ
	//★★  超えない: CPUが有効である、サブCPUを起動する -> ブランチ命令の先へ
3:	bge	s6, s7, _start_hang
	//...
OpenSBIのロゴが出たあたりでブレークして各スレッドの実行している関数名を見ると、下記のようになります。
(gdb) info thr
  Id   Target Id                    Frame
  1    Thread 1.1 (CPU#0 [halted ]) sbi_hsm_hart_wait (scratch=0x8008c000, hartid=0)
    at opensbi/lib/sbi/sbi_hsm.c:177
  2    Thread 1.2 (CPU#1 [running]) 0x0000000080020ffe in fdt32_to_cpu (x=50331648)
    at opensbi/lib/utils/libfdt/libfdt_env.h:57
* 3    Thread 1.3 (CPU#2 [halted ]) _start_hang ()
    at opensbi/firmware/fw_base.S:409
  4    Thread 1.4 (CPU#3 [halted ]) sbi_hsm_hart_wait (scratch=0x80088000, hartid=3)
    at opensbi/lib/sbi/sbi_hsm.c:177
Thread Id 3つまりCPU 2(※)だけ_start_hang()に居ることがわかりますね。
(※)GDBのThread Idは1スタートなのでThread Id 1, 2, 3, 4がCPU 0, 1, 2, 3に相当します。
 この記事にコメントする
 この記事にコメントする
目次: Java
目次: 一覧の一覧
 この記事にコメントする
 この記事にコメントする
| < | 2024 | > | ||||
| << | < | 10 | > | >> | ||
| 日 | 月 | 火 | 水 | 木 | 金 | 土 | 
| - | - | 1 | 2 | 3 | 4 | 5 | 
| 6 | 7 | 8 | 9 | 10 | 11 | 12 | 
| 13 | 14 | 15 | 16 | 17 | 18 | 19 | 
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | 
| 27 | 28 | 29 | 30 | 31 | - | - | 
 25年10月6日
 25年10月6日
 25年10月6日
 25年10月6日
 25年9月29日
 25年9月29日
 25年9月29日
 25年9月29日
 20年8月24日
 20年8月24日
 20年8月24日
 20年8月24日
 16年2月14日
 16年2月14日
 16年2月14日
 16年2月14日
 25年7月20日
 25年7月20日
 25年7月20日
 25年7月20日
 25年7月20日
 25年7月20日
 25年7月20日
 25年7月20日
 25年7月20日
 25年7月20日
 25年7月20日
 25年7月20日
 20年8月16日
 20年8月16日
 20年8月16日
 20年8月16日
 20年8月16日
 20年8月16日
 20年8月16日
 20年8月16日
 24年6月17日
 24年6月17日
 24年6月17日
 24年6月17日
 wiki
 wiki Linux JM
 Linux JM Java API
 Java API 2002年
 2002年 2003年
 2003年 2004年
 2004年 2005年
 2005年 2006年
 2006年 2007年
 2007年 2008年
 2008年 2009年
 2009年 2010年
 2010年 2011年
 2011年 2012年
 2012年 2013年
 2013年 2014年
 2014年 2015年
 2015年 2016年
 2016年 2017年
 2017年 2018年
 2018年 2019年
 2019年 2020年
 2020年 2021年
 2021年 2022年
 2022年 2023年
 2023年 2024年
 2024年 2025年
 2025年 過去日記について
 過去日記について アクセス統計
 アクセス統計 サーバ一覧
 サーバ一覧 サイトの情報
 サイトの情報合計: 
本日: