今天,我们很高兴地宣布 Fedora Asahi Remix 39 现已 正式发布!这个 Remix 版基于 Fedora Linux 39,通过广泛的平台和设备支持,在苹果芯片 Mac 上提供了出色的体验。
作者: 默默
Musl libc:为什么我们会需要另一个 libc?
如果你是一个 Linux 用户,那你一定至少听说过 Glibc 的鼎鼎大名,或者甚至在日常使用中碰到不少关于它的问题,例如 Glibc 版本不匹配等问题。而本文的主角—— Musl libc 与之相比就要默默无闻的多,毕竟绝大多数的 Linux 发行版使用的 libc 库都是 Glibc,只有 Alpine Linux 等极少数 Linux 发行版才会使用 Musl libc,并且还会遇到诸如闭源 JDK 无法正常使用等问题的困扰,那么,为什么我们会需要另一个 libc 函数库呢?
Glibc 并不完美
Glibc 作为目前使用最广泛的 libc 函数库,虽然拥有最广泛的发行版支持和用户群体,并且在兼容性和性能方面也存在一些优势,但它并不完美,有三个问题严重困扰着它。
代码库陈旧
Glibc 拥有悠久的历史——对于软件而言这可能并不一定是一句赞誉,尤其是当你需要处理上世纪九十年代就存在的代码库时。三十多年来,程序员们编写 C 程序的方式并不是一成不变的,某些在那个年代被认为是好习惯或者是必须的编程方式在今天看来可能完全不合时宜,诸如叠床架屋的宏等等。这些问题严重拖累了 Glibc 的源码可读性,例如下面这一段源代码,摘自 Glibc 的 fopen 函数。
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 |
FILE * __fopen_internal (const char *filename, const char *mode, int is32) { struct locked_FILE { struct _IO_FILE_plus fp; #ifdef _IO_MTSAFE_IO _IO_lock_t lock; #endif struct _IO_wide_data wd; } *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE)); if (new_f == NULL) return NULL; #ifdef _IO_MTSAFE_IO new_f->fp.file._lock = &new_f->lock; #endif _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps); _IO_JUMPS (&new_f->fp) = &_IO_file_jumps; _IO_new_file_init_internal (&new_f->fp); if (_IO_file_fopen ((FILE *) new_f, filename, mode, is32) != NULL) return __fopen_maybe_mmap (&new_f->fp.file); _IO_un_link (&new_f->fp); free (new_f); return NULL; } |
作为对比,下面是 Musl 库中同样对 fopen 函数的实现
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 |
FILE *fopen(const char *restrict filename, const char *restrict mode) { FILE *f; int fd; int flags; /* Check for valid initial mode character */ if (!strchr("rwa", *mode)) { errno = EINVAL; return 0; } /* Compute the flags to pass to open() */ flags = __fmodeflags(mode); fd = sys_open(filename, flags, 0666); if (fd < 0) return 0; if (flags & O_CLOEXEC) __syscall(SYS_fcntl, fd, F_SETFD, FD_CLOEXEC); f = __fdopen(fd, mode); if (f) return f; __syscall(SYS_close, fd); return 0; } |
两者的可读性差距不言自明,笔者在这里并非是要批判 Glibc 的代码风格,但对于初次上手阅读源码的人来说,显然是 Musl 的风格更加友好,更便于理解。
体积过大
由于 Glibc 相较体积更加关注性能,因此其链接生成的二进制文件相较于 Musl uClibc 等专注于嵌入式等场合的库来说要大很多,而这些场合往往非常关注几百 K 大小的区别,因为 SRAM 的大小往往关乎整体开发板的成本。
下面这张表展示了不同 libc 库编译的文件大小。
尺寸对比 | musl | uClibc | dietlibc | glibc |
---|---|---|---|---|
.a | 426k | 500k | 120k | 2.0M |
.so | 527k | 560k | 185k | 7.9M |
静态最小 | 1.8k | 5k | 0.2k | 662k |
静态输出 | 13k | 70k | 6k | 662k |
可以看出 Glibc 在程序大小上明显大于其他 libc 库,此外,这里的静态也是有水分的,这就引出了 Glibc 的下一个问题,也是最重要的问题之一:静态链接。
对静态链接支持不佳
理论上来说,Glibc 是支持静态链接的。但,这也仅仅是从理论上来说,由于一些历史遗留问题(当然,也包括对功能实现的考虑)Glibc 的静态链接并不是真正的静态链接:如果你的程序中使用了某些不支持静态链接的特性(这一点在大型软件中非常常见),那么即便你在链接时选择静态链接,生成出来的程序实际上仍然是依赖于 Glibc 动态库的,一旦你尝试删除掉它,你立马就会发现这些“静态”链接的程序统统罢工不干了。
谁在用 Musl?
在发行版中,主要是 Alpine Linux,作为最特立独行的 Linux 发行版之一,它选用了 Musl + Busybox 的组合,而非通常的 Glibc + Coreutils,这使得它的最小安装可以控制在惊人的 5 MB 之内!相比之下,普通的 CentOS 最小安装则需要 200 MB 左右,这一点使得它在嵌入式等对内存占用极为敏感的场合占据了相当的优势。
此外,Musl 从设计之初就很关注静态链接的可用性,因此它完全可以被静态链接进其他程序中,不存在 Glibc 对动态库的依赖问题,这一点也有助于缓解不同版本 libc 之间的兼容性问题——只要我把万物都静态链接进去,就不存在版本问题了。当然,这种做法也会带来体积膨胀等问题,所以并不是一个太好的解决方案。
Musl 的问题与未来
虽然拥有诸多优点,但 Musl 在性能方面逊于 Glibc 也是不争的事实,毕竟简化实现的代价就包括牺牲性能,不过这一点并非不可拯救,通过使用开源的 malloc 实现(诸如微软的实现)替换这些对性能影响较大的热点函数,就可以在很大程度上解决性能方面的问题。
同时,试图取代或至少部分取代 Glibc 的库也并不止 Musl 一个, 例如用于 AOSP 项目上的 bionic,以及广泛应用于各种嵌入式开发中的 uClibc 等。与它们相比,Musl 背后既没有 Google 这样的大公司撑腰,在压缩体积方面做的也不够极致,相较之下就没有那么受到开发者们的青睐,在新功能和新特性跟进上也不是非常积极。
出于这些原因,也许 Musl 在今后的很长一段时间内会继续保持这种“小而美”的特点,但这对于我们来说并非就是一件坏事,能够看到与 GNU Glibc 风格截然不同的另一种实现,对于 Linux 社区的多样性,以及对于我们这些学习者来说,何尝不是一件美事?
参考链接
Box86/Box64 vs QEMU vs FEX (vs Rosetta2)
Comparing performances
I decided to compare the performances of the OpenSource Linux Userspace Emulator that allows you to run x86/x86_64 apps on ARM linux. There are QEMU-user, FEX-emu and Box86/Box64.
How to bench Linux userspace emulator
Test will consist of the bench I already used a couple of time, and that can be run as native or emulated:
- 7-zip integrated benchmark, that contains mostly integer code (no x87 or SSE), and can be used as a baseline to see the pure x86 code translation efficiency. The version 16 present in Ubuntu was used for those tests.
- dav1d, an opensource video transcoding tool, that includes hand-optimized SSE assembly code (SSE3 or more).
- glmark2 that is GL limited and should run at mostly native speed (as long as GL is hardware accelerated). I couldn’t install the armhf version of glmark2 on Ubuntu, so only the native 64bits version was benchmarked.
- openarena, that contains x87 code, and a JIT, and, in that config, is very much GPU limited, and so should be running very close to the native speed (again, as long as GL is still hardware accelerated).
The 7z, dav1d and glmark2 bench are described here, and the openarena one there.
I’ll also do some quick bench not available natively. The fps will simply be measured with HUD_GALLIUM=fps
on a stable and reproducible moment in the game:
- WorldOfGoo: The game has simple graphics, it should run fine. Measures will be done on the Title screen.
- FTL: that I added to the bench after doing the QEMU measures. Measures will be done on the 1st Tutorial screen, while the game is paused.
- CINEBENCH r15: A benchmark based on a raytracing engine. Lots of SSE (SSE2 and more) code here. Use multi-core also. Does include a CPU bench and OpenGL bench, but only the CPU one is used here. It provides a simple number indicating the performance (the higher, the better).
To install WorldOfGoo, the “uname” trick will be used, as this allows to choose x86 or x86_64 installation (without the trick, the installer doesn’t recognise “aarch64” platform and fallback to x86). WorldOfGoo will run at 1920×1080 fullscreen.
CINEBENCH r15. This one needs Wine, and a 64bits version of it. It’s a benchmark with the CINEMA 4D Engine.
After some some testing, I realized that both openarena and WorldOfGoo mainly use x87 code, at least for the 32bits version of WorldOfGoo. Both QEMU and FEX seem to use use Softfloat for it, to keep the 80bits precision, while box uses hardware float (with some tricks to keep 80bits when needed, like on some data copy used by old games), so I decided to also check the menu page of FTL, that I know use SSE code. But I didn’t test on QEMU (it’s not hardware accelerated anyway, so it would be too slow). FTL will run at default resolution of 1280×720 windowed. I’ll launch the tutorial, answer to the 1st dialog box and mesure the fps at this point.
iPhone/iPad设备型号对应常用名称列表(2023更新至iPhone 15 Pro Max | iPad Air 5 | iPad10 | iPad Pro 12.9-inch 6)
设备型号 | 名称 |
---|---|
iPhone3,1 | iPhone 4 |
iPhone3,2 | iPhone 4 |
iPhone3,3 | iPhone 4 |
iPhone4,1 | iPhone 4S |
iPhone5,1 | iPhone 5 |
iPhone5,2 | iPhone 5 |
iPhone5,3 | iPhone 5c |
iPhone5,4 | iPhone 5c |
iPhone6,1 | iPhone 5s |
iPhone6,2 | iPhone 5s |
iPhone7,1 | iPhone 6 Plus |
iPhone7,2 | iPhone 6 |
iPhone8,1 | iPhone 6s |
iPhone8,2 | iPhone 6s Plus |
iPhone8,4 | iPhone SE |
iPhone9,1 | iPhone 7 |
iPhone9,2 | iPhone 7 Plus |
iPhone9,3 | iPhone 7 |
iPhone9,4 | iPhone 7 Plus |
iPhone10,1 | iPhone 8 |
iPhone10,2 | iPhone 8 Plus |
iPhone10,4 | iPhone 8 |
iPhone10,5 | iPhone 8 Plus |
iPhone10,3 | iPhone X |
iPhone10,6 | iPhone X |
iPhone11,2 | iPhone XS |
iPhone11,4 | iPhone XS Max |
iPhone11,6 | iPhone XS Max |
iPhone11,8 | iPhone XR |
iPhone12,1 | iPhone 11 |
iPhone12,3 | iPhone 11 Pro |
iPhone12,5 | iPhone 11 Pro Max |
iPhone12,8 | iPhone SE 2 |
iPhone13,1 | iPhone 12 mini |
iPhone13,2 | iPhone 12 |
iPhone13,3 | iPhone 12 Pro |
iPhone13,4 | iPhone 12 Pro Max |
iPhone14,4 | iPhone 13 mini |
iPhone14,5 | iPhone 13 |
iPhone14,2 | iPhone 13 Pro |
iPhone14,3 | iPhone 13 Pro Max |
iPhone14,6 | iPhone SE 3 |
iPhone14,7 | iPhone 14 |
iPhone14,8 | iPhone 14 Plus |
iPhone15,2 | iPhone 14 Pro |
iPhone15,3 | iPhone 14 Pro Max |
iPhone15,4 | iPhone 15 |
iPhone15,5 | iPhone 15 Plus |
iPhone16,1 | iPhone 15 Pro |
iPhone16,2 | iPhone 15 Pro Max |
i386 | Simulator |
x86_64 | Simulator |
iPod1,1 | iPod Touch 1G |
iPod2,1 | iPod Touch 2G |
iPod3,1 | iPod Touch 3G |
iPod4,1 | iPod Touch 4G |
iPod5,1 | iPod Touch 5G |
iPod7,1 | iPod Touch 6G |
iPod9,1 | iPod Touch 7G |
iPad1,1 | iPad |
iPad1,2 | iPad 3G |
iPad2,1 | iPad 2 |
iPad2,2 | iPad 2 |
iPad2,3 | iPad 2 |
iPad2,4 | iPad 2 |
iPad2,5 | iPad Mini |
iPad2,6 | iPad Mini |
iPad2,7 | iPad Mini |
iPad3,1 | iPad 3 |
iPad3,2 | iPad 3 |
iPad3,3 | iPad 3 |
iPad3,4 | iPad 4 |
iPad3,5 | iPad 4 |
iPad3,6 | iPad 4 |
iPad4,1 | iPad Air |
iPad4,2 | iPad Air |
iPad4,3 | iPad Air |
iPad4,4 | iPad Mini 2 |
iPad4,5 | iPad Mini 2 |
iPad4,6 | iPad Mini 2 |
iPad4,7 | iPad Mini 3 |
iPad4,8 | iPad Mini 3 |
iPad4,9 | iPad Mini 3 |
iPad5,1 | iPad Mini 4 |
iPad5,2 | iPad Mini 4 |
iPad5,3 | iPad Air 2 |
iPad5,4 | iPad Air 2 |
iPad6,3 | iPad Pro 9.7 |
iPad6,4 | iPad Pro 9.7 |
iPad6,7 | iPad Pro 12.9 |
iPad6,8 | iPad Pro 12.9 |
iPad6,11 | iPad 5 |
iPad6,12 | iPad 5 |
iPad7,1 | iPad Pro 12.9 inch 2nd gen |
iPad7,2 | iPad Pro 12.9 inch 2nd gen |
iPad7,3 | iPad Pro 10.5 inch |
iPad7,4 | iPad Pro 10.5 inch |
iPad7,5 | iPad 6 |
iPad7,6 | iPad 6 |
iPad7,11 | iPad 7 |
iPad7,12 | iPad 7 |
iPad8,1 ~ 8,4 | iPad Pro 11-inch |
iPad8,5 ~ 8,8 | iPad Pro 12.9-inch 3rd gen |
iPad8,9 ~ 8,10 | iPad Pro 11-inch 2nd gen |
iPad8,11 ~ 8,12 | iPad Pro 12.9-inch 4th gen |
iPad11,1 | iPad Mini 5 |
iPad11,2 | iPad Mini 5 |
iPad11,3 | iPad Air 3 |
iPad11,4 | iPad Air 3 |
iPad11,6 | iPad 8 |
iPad11,7 | iPad 8 |
iPad13,1 | iPad Air 4 |
iPad13,2 | iPad Air 4 |
iPad12,1 | iPad 9 |
iPad12,2 | iPad 9 |
iPad14,1 | iPad Mini 6 |
iPad14,2 | iPad Mini 6 |
iPad13,4 ~ 13,7 | iPad Pro 11-inch 3nd gen |
iPad13,8 ~ 13,11 | iPad Pro 12.9-inch 5th gen |
iPad13,16 | iPad Air 5 |
iPad13,17 | iPad Air 5 |
iPad13,18 | iPad 10 |
iPad13,19 | iPad 10 |
iPad14,3 ~ 14,4 | iPad Pro 11-inch 4th gen |
iPad14,5 ~ 14,6 | iPad Pro 12.9-inch 6th gen |
参考链接
OpenSSL通过OCSP手动验证证书
这篇文章主要用来说明如何借助ocsp服务器来验证证书。ocsp(The Online Certificate Status Protocol)是一种验证证书状态的一种方式,也是CRL(certificate revocation list)证书吊销的一种替代方式。
与传统的CRL比较有以下特点:
- 由于相对于传统的CRL,一个ocsp响应包含的信息更少,故ocsp能够更有效利用网络和客户资源
- 用OCSP,客户无需自己解析CRL证书吊销列表,但是客户需要存储状态信息,而由于客户侧需要维护存储缓存,故导致存储信息很复杂。在实际使用中,这点带来的影响却很小,由于第三库提供的相关接口已经帮我们完成此类工作
- OCSP通过专用网络、专用证书、在特定的时间公开其服务。OCSP不强制加密,故可能带来信息泄露的风险。
此文章中用到的openssl的版本为:OpenSSL 1.0.1g 7 Apr 2014
1、获取证书用于ocsp验证
首先,我们将从一个网站上获取一个证书,这里我们用Wikipedia作为样例来进行。我们获取证书通过如下命令:
1 |
$ openssl s_client -connect wikipedia.org:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' |
过该命令可以获取wikipedia.org的客户端证书
保存这个输出到wikipedia.pem文件中
1 |
$ openssl s_client -connect wikipedia.org:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > wikipedia.pem |
现在,检查整个证书中是否包含ocsp网址
1 |
$ openssl x509 -noout -ocsp_uri -in wikipedia.pem |
若执行正确则输出 http://ocsp.digicert.com
,否则你就不能通过ocsp验证这个证书
2、获取证书链
由于这个证书认证是一级一级逐层进行,故需要获得与这个证书相关的证书链。利用openssl s_client -showcerts 选项,能够查看到在该信任链上的所有相关证书
1 |
$ openssl s_client -connect wikipedia.org:443 -showcerts 2>&1 < /dev/null |
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 32 33 34 35 36 37 38 |
1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance CA-3 i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA -----BEGIN CERTIFICATE----- MIIGWDCCBUCgAwIBAgIQCl8RTQNbF5EX0u/UA4w/OzANBgkqhkiG9w0BAQUFADBs MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j ZSBFViBSb290IENBMB4XDTA4MDQwMjEyMDAwMFoXDTIyMDQwMzAwMDAwMFowZjEL MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug Q0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdR CPge+yLtYb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcv KEZmBMcqeSZ6mdWOw21PoF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5 BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fmXcCabH4JnudSREoQOiPkm7YDr6ictFuf 1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6RlgWYw7If48hl66l7XaAs zPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp496+ynaF4d 32duXvsCAwEAAaOCAvowggL2MA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0w ggG5MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3 LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUH AgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQBy AHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBj AGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAg AEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBt AGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBj AG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBl AHIAZQBuAGMAZQAuMBIGA1UdEwEB/wQIMAYBAf8CAQAwNAYIKwYBBQUHAQEEKDAm MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgY8GA1UdHwSB hzCBhDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGln aEFzc3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNl cnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSME GDAWgBSxPsNpA/i/RwHUmCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUB INTeeZlIg/cwDQYJKoZIhvcNAQEFBQADggEBAB7ipUiebNtTOA/vphoqrOIDQ+2a vD6OdRvw/S4iWawTwGHi5/rpmc2HCXVUKL9GYNy+USyS8xuRfDEIcOI3ucFbqL2j CwD7GhX9A61YasXHJJlIR0YxHpLvtF9ONMeQvzHB+LGEhtCcAarfilYGzjrpDq6X dF3XcZpCdF/ejUN83ulV7WkAywXgemFhM9EZTfkI7qA5xSU1tyvED7Ld8aW3DiTE JiiNeXf1L/BXunwH1OH8zVowV36GEEfdMR/X/KLCvzB8XSSq6PmuX2p0ws5rs0bY Ib4p1I5eFdZCSucyb6Sxa1GDWL4/bcf72gMhy2oWGU4K8K2Eyl2Us1p292E= -----END CERTIFICATE----- |
如你所见,输出能够看到两个证书,number 1 和number 0,其中number 0就是我们刚刚获取的那个证书。如果你的网站有更多证书在认证链中,那么你将看到更多证书。为了发送证书,需要保存证书链中所有证书到一个文件chain.pem,按照刚刚命令输出的证书顺序,根证书总是在文件结尾。
3、发送ocsp认证请求
现在我们有ocsp认证请求的所有信息,使用下面命令发送ocsp认证请求。
1 |
$ openssl ocsp -issuer chain.pem -cert wikipedia.pem -text -url http://ocsp.digicert.com |
其结果如下
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
OCSP Request Data: Version: 1 (0x0) Requestor List: Certificate ID: Hash Algorithm: sha1 Issuer Name Hash: ED48ADDDCB7B00E20E842AA9B409F1AC3034CF96 Issuer Key Hash: 50EA7389DB29FB108F9EE50120D4DE79994883F7 Serial Number: 0114195F66FAFF8FD66E12496E516F4F Request Extensions: OCSP Nonce: 0410DA634F2ADC31DC48AE89BE64E8252D12 OCSP Response Data: OCSP Response Status: successful (0x0) Response Type: Basic OCSP Response Version: 1 (0x0) Responder Id: 50EA7389DB29FB108F9EE50120D4DE79994883F7 Produced At: Apr 9 08:45:00 2014 GMT Responses: Certificate ID: Hash Algorithm: sha1 Issuer Name Hash: ED48ADDDCB7B00E20E842AA9B409F1AC3034CF96 Issuer Key Hash: 50EA7389DB29FB108F9EE50120D4DE79994883F7 Serial Number: 0114195F66FAFF8FD66E12496E516F4F Cert Status: good This Update: Apr 9 08:45:00 2014 GMT Next Update: Apr 16 09:00:00 2014 GMT Signature Algorithm: sha1WithRSAEncryption 56:21:4c:dc:84:21:f7:a8:ac:a7:b9:bc:10:19:f8:19:f1:34: c1:63:ca:14:7f:8f:5a:85:2a:cc:02:b0:f8:b5:05:4a:0f:28: 50:2a:4a:4d:04:01:b5:05:ef:a5:88:41:d8:9d:38:00:7d:76: 1a:aa:ff:21:50:68:90:d2:0c:93:85:49:e7:8e:f1:58:08:77: a0:4e:e2:22:98:01:b7:e3:27:75:11:f5:b7:8f:e0:75:7d:19: 9b:74:cf:05:dc:ae:1c:36:09:95:b6:08:bc:e7:3f:ea:a2:e3: ae:d7:8f:c0:9d:8e:c2:37:67:c7:5b:d8:b0:67:23:f1:51:53: 26:c2:96:b0:1a:df:4e:fb:4e:e3:da:a3:98:26:59:a8:d7:17: 69:87:a3:68:47:08:92:d0:37:04:6b:49:9a:96:9d:9c:b1:e8: cb:dc:68:7b:4a:4d:cb:08:f7:92:67:41:99:b6:54:56:80:0c: 18:a7:24:53:ac:c6:da:1f:4d:f4:3c:7d:68:44:1d:a4:df:1d: 48:07:85:52:86:59:46:d1:35:45:1a:c7:6b:6b:92:de:24:ae: c0:97:66:54:29:7a:c6:86:a6:da:9f:06:24:dc:ac:80:66:95: e0:eb:49:fd:fb:d4:81:6a:2b:81:41:57:24:78:3b:e0:66:70: d4:2e:52:92 wikipedia.pem: good This Update: Apr 9 08:45:00 2014 GMT Next Update: Apr 16 09:00:00 2014 GMT |
如果你需要更简略的输出,去掉-text 选项,该选项一般用于调试
1 2 3 4 |
$ openssl ocsp -issuer chain.pem -cert wikipedia.pem -url http://ocsp.digicert.com wikipedia.pem: good This Update: Apr 9 08:45:00 2014 GMT Next Update: Apr 16 09:00:00 2014 GMT |
4、吊销证书
如果你有一个吊销的证书,你也可以测试该证书按照上述步骤,所得的响应如下:
1 2 3 4 5 |
Response verify OK test-revoked.pem: revoked This Update: Apr 9 03:02:45 2014 GMT Next Update: Apr 10 03:02:45 2014 GMT Revocation Time: Mar 25 15:45:55 2014 GMT |
5、其他错误
如果证书和ocsp服务不匹配,验证将错误,使用-text选项可以查看具体错误。
参考链接
Flutter 3.16中WillPopScope过期使用PopScope来代替
Flutter 3.16 中 WillPopScope 过期了,需要使用 PopScope 来代替。
针对 PopScope 的 canPop 参数,官方文档解释如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/// Manages system back gestures. /// /// The [canPop] parameter can be used to disable system back gestures. Defaults /// to true, meaning that back gestures happen as usual. /// /// The [onPopInvoked] parameter reports when system back gestures occur, /// regardless of whether or not they were successful. /// /// If [canPop] is false, then a system back gesture will not pop the route off /// of the enclosing [Navigator]. [onPopInvoked] will still be called, and /// `didPop` will be `false`. /// /// If [canPop] is true, then a system back gesture will cause the enclosing /// [Navigator] to receive a pop as usual. [onPopInvoked] will be called with /// `didPop` as `true`, unless the pop failed for reasons unrelated to /// [PopScope], in which case it will be `false`. |
当 canPop 为 false,则执行系统返回时会被拦截,并且调用 onPopInvoked 方法,同时 didPop 为 false,此时进行逻辑判断,如果需要返回则执行 Navigator.of(context).pop(); 。
注意此时 onPopInvoked 又会被调用,并且 didPop 为 true。
参考Demo: github.com
示例代码如下:
修改之前的代码( WillPopScope )如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This sample demonstrates showing a confirmation dialog before navigating // away from a page. import 'package:flutter/material.dart'; void main() => runApp(const NavigatorPopHandlerApp()); class NavigatorPopHandlerApp extends StatelessWidget { const NavigatorPopHandlerApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( initialRoute: '/home', routes: <String, WidgetBuilder>{ '/home': (BuildContext context) => const _HomePage(), '/two': (BuildContext context) => const _PageTwo(), }, ); } } class _HomePage extends StatefulWidget { const _HomePage(); @override State<_HomePage> createState() => _HomePageState(); } class _HomePageState extends State<_HomePage> { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('Page One'), TextButton( onPressed: () { Navigator.of(context).pushNamed('/two'); }, child: const Text('Next page'), ), ], ), ), ); } } class _PageTwo extends StatefulWidget { const _PageTwo(); @override State<_PageTwo> createState() => _PageTwoState(); } class _PageTwoState extends State<_PageTwo> { void _showBackDialog() { showDialog<void>( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Are you sure?'), content: const Text( 'Are you sure you want to leave this page?', ), actions: <Widget>[ TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), child: const Text('Nevermind'), onPressed: () { Navigator.pop(context); }, ), TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), child: const Text('Leave'), onPressed: () { Navigator.pop(context); Navigator.pop(context); }, ), ], ); }, ); } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () { _showBackDialog(); return false; }, child: Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('Page Two'), TextButton( onPressed: () { _showBackDialog(); }, child: const Text('Go back'), ), ], ), ))); } } |
修改之后的代码( PopScope )如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This sample demonstrates showing a confirmation dialog before navigating // away from a page. import 'package:flutter/material.dart'; void main() => runApp(const NavigatorPopHandlerApp()); class NavigatorPopHandlerApp extends StatelessWidget { const NavigatorPopHandlerApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( initialRoute: '/home', routes: <String, WidgetBuilder>{ '/home': (BuildContext context) => const _HomePage(), '/two': (BuildContext context) => const _PageTwo(), }, ); } } class _HomePage extends StatefulWidget { const _HomePage(); @override State<_HomePage> createState() => _HomePageState(); } class _HomePageState extends State<_HomePage> { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('Page One'), TextButton( onPressed: () { Navigator.of(context).pushNamed('/two'); }, child: const Text('Next page'), ), ], ), ), ); } } class _PageTwo extends StatefulWidget { const _PageTwo(); @override State<_PageTwo> createState() => _PageTwoState(); } class _PageTwoState extends State<_PageTwo> { void _showBackDialog() { showDialog<void>( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Are you sure?'), content: const Text( 'Are you sure you want to leave this page?', ), actions: <Widget>[ TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), child: const Text('Nevermind'), onPressed: () { Navigator.pop(context); }, ), TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.labelLarge, ), child: const Text('Leave'), onPressed: () { Navigator.pop(context); Navigator.pop(context); }, ), ], ); }, ); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('Page Two'), PopScope( canPop: false, onPopInvoked: (bool didPop) { if (didPop) { return; } _showBackDialog(); }, child: TextButton( onPressed: () { _showBackDialog(); }, child: const Text('Go back'), ), ), ], ), ), ); } } |
参考链接
Flutter的Don't use BuildContext's across async gaps警告解决方法
问题
Flutter开发中遇到Don't use BuildContext's across async gaps警告
有问题的源码
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 |
if (await databaseHelper.isDataExist(task.title)) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text("已存在同名配置"), content: Text("是否覆盖已有的配置?"), actions: <Widget>[ ElevatedButton( child: const Text("取消"), onPressed: () { Navigator.of(context).pop(); }, ), ElevatedButton( child: const Text("确认"), onPressed: () async { Navigator.of(context).pop(); databaseHelper.updateDatabaseByTitle(task); }, ), ], ); }, ); } |
问题原因
“不要在异步间隙(async gaps)中使用 BuildContext” 是一个Flutter中的常见警告消息,通常表示你正在尝试在异步操作中访问 BuildContext,这是一个不推荐的做法,因为它可能引发不确定的行为或错误。
如果在将上下文传递给AlertDialog后导航堆栈发生更改,并且尝试使用旧上下文再次导航,则会出现错误。
问题分析
Context的含义
Flutter中的 BuildContext 和 Context 是相同的,BuildContext 是 Context 的别名。这两个术语用来表示小部件树中的位置信息和上下文环境,用于在构建小部件树和访问资源(例如主题、本地化、导航等)时提供上下文信息。
在Flutter中,BuildContext 或 Context 表示的是一个由小部件树组成的层次结构中的位置。每个小部件都有一个与之相关的 BuildContext,这个上下文包含有关小部件的信息,例如其位置、父级小部件、主题数据等等。
尽管 Context 和 BuildContext 是相同的类型,但通常我们更倾向于使用 BuildContext 这个术语,因为它更明确地表示它是与构建过程相关的上下文。
BuildContext的作用
BuildContext 类型通常用于以下操作:
- 访问父级小部件:你可以使用 BuildContext 访问小部件树中的父级小部件,这对于在小部件之间传递数据和状态非常有用。
- 获取主题数据:通过 BuildContext 可以访问当前主题的数据,如颜色、字体、间距等。
- 获取本地化信息:你可以使用 BuildContext 获取本地化信息,以根据用户的语言偏好来显示文本。
- 导航:BuildContext 通常用于导航操作,如推送新路由或弹出对话框。
- 构建小部件:BuildContext 是在小部件的 build 方法中传递的,它告诉小部件在小部件树中的位置。
BuildContext 和 Context 都代表了小部件树中的位置和上下文信息,它们在构建和交互中扮演着关键的角色,但它们实际上是相同的概念的不同表达方式。因此,你可以放心地将它们视为等同的,使用其中一个作为标识符,以便更清晰地表示其作用。
特殊情况
然而,在某些情况下,你可能需要在异步操作中访问 BuildContext,例如在异步回调中执行 UI 操作。这通常是不安全的,因为异步操作可能会在 BuildContext 不再有效的情况下执行,从而引发错误。
解决方法
1 |
if (context.mounted) Navigator.of(context).pop(); |
不要在异步间隙中直接使用 BuildContext,因为它可能会导致不安全的操作。使用提供的方法来安全地查找小部件并在异步操作中访问它们的上下文。这可以帮助你避免潜在的问题和错误。
官方说明如下:
Details
DON’T use BuildContext across asynchronous gaps.
Storing BuildContext
for later usage can easily lead to difficult to diagnose crashes. Asynchronous gaps are implicitly storing BuildContext
and are some of the easiest to overlook when writing code.
When a BuildContext
is used, its mounted
property must be checked after an asynchronous gap.
BAD:
1 2 3 4 |
void onButtonTapped(BuildContext context) async { await Future.delayed(const Duration(seconds: 1)); Navigator.of(context).pop(); } |
GOOD:
1 2 3 |
void onButtonTapped(BuildContext context) { Navigator.of(context).pop(); } |
GOOD:
1 2 3 4 5 6 |
void onButtonTapped() async { await Future.delayed(const Duration(seconds: 1)); if (!context.mounted) return; Navigator.of(context).pop(); } |
参考链接
性能有坑 | 慎用 Java 8 ConcurrentHashMap 的 computeIfAbsent
前言
我们先看一段代码,代码中使用 Map 的时候,有可能会这么写:
1 2 3 4 5 6 7 8 |
Map<String, Value> map; // ... Value result = map.get(key); if (null == result) { result = this.calculateValue(key); map.put(key, result); } return result; |
Java 8 的 java.util.Map 里面有个方法 computeIfAbsent,能够简化以上代码:
1 2 3 |
Map<String, Value> map; // ... return map.computeIfAbsent(key, this::calculateValue); |
以上这种写法除了简洁,如果使用的是 java.util.concurrent.ConcurrentHashMap,还能够在并发调用的情况下确保 calculateValue 方法不会被重复调用,保证原子性。
不过,前段时间对 Apache ShardingSphere-Proxy 做压测时遇到一个问题,当 BenchmarkSQL 连接 ShardingSphere Proxy 的 Terminal 数量比较高时,其中一条很简单的插入 SQL 执行延迟增加了很多。借助 Async Profiler 发现 Java 8 ConcurrentHashMap 的 computeIfAbsent 在性能上有坑。
不了解 Apache ShardingSphere 的读者可以参考 https://github.com/apache/shardingsphere。
解决Windows 10备份和还原遇到的0x80070544问题
随着主力计算机设备年限越来越近,对数据保护的重视度也越来越高,尤其之前遭遇过数据损失。目前采用的备份方案是使用文件历史记录功能对 OneDrive 等经常会访问和编辑的目录进行备份。对于其他归档用途的目录则使用备份和还原功能,定期执行一次备份。而备份的位置建议是额外的磁盘或 NAS 提供的 iSCSI,gOxiA 为了图方便和节省 NAS 空间,则使用的是共享文件夹的方式。在实际配置过程中如果使用共享文件夹这样的网络位置,则可能会遇到 0x80070544 故障问题,提示为“请求的验证信息类无效”。
Android:Installed Build Tools revision 33.0.2 is corrupted.
1 |
Remove and install again using the SDK Manager. |
使用33.0.2及以上版本的build-tools编译Android应用时。
有些人会按照提示去SDK Manager中重新安装build tools,然后发现这样做是无用的
编译时会收到:
Windows:
1 |
Build-tool 33.0.2 is missing DX at D:\Sdk\build-tools\33.0.2\dx.bat |
Linux/macOS:
1 |
Build-tool 33.0.2 is missing DX |
解决方案:
更改批处理文件名称
Windows系统:
- 找到build tools目录中的d8.bat,将文件名修改为dx.bat。
- 找到build tools目录中的lib/d8.jar,将文件名修改为dx.jar。
- 回到Android Studio重新打包。
Linux/macOS系统:
- 找到build tools目录中的d8,创建软链接 ln -s d8 dx。
- 找到build tools目录中的lib/d8.jar,创建软链接 ln -s d8.jar dx.jar。
- 回到Android Studio重新打包。