基础软件技术基础

852 阅读11分钟

基础软件技术基础

技术点列举

  1. 领域常识
  2. git
  3. linux
  4. C
  5. 图吧垃圾佬

领域常识

  1. 指令集: 众所周知, 计算机的本质是由0和1组成的, 而这些0和1的排列规则就是指令集的本质, 比如以001101开头的32个0和1组成的数, 就是mips的一条指令格式, 许多条这种格式在一起, 就是指令集. 目前比较常听到的指令集包括arm64, amd64, x86_64, riscv, sw64, mips, loongarch等. 这些指令集具有相关专利, 指令集可控通常认为使用此指令集无需付费, 可以随意修改指令格式.

  2. 指令集架构: 上述许多指令形成了的指令集, 足够多的指令之间互相配合形成一种结构, 也就是指令集架构, 与指令集自主可控的区别是, 指令集架构自主可控仍然需要满足其对外提供的接口, 也就是ABI不能变. 就像人一只手拿一个苹果, 指令集架构可控可以让人在手肘处长出一个篮子, 装很多苹果, 但是一只手还是只能拿一个苹果.

  3. ip内核: 指令集只是一团数字, 而用在我们的电脑上, 还需要由程序员将其写成代码, 程序员最终写成的代码, 就是ip核, 全称:intellectual property core. 而所谓的ip核架构自主可控就是使用这个ip核无需付费, 可以任意的去组装, 但是里面的东西不可以修改.

  4. 通用CPU: 一般指的是服务器用和桌面计算用CPU芯片, 我们常用的电脑芯片, 还有超星学习通服务器使用的芯片, 都属于通用CPU

  5. 超算: 即超级计算机, 可以有很多个CPU和GPU, 通常用于大型复杂项目的处理.

  6. 精简指令集: 指令的格式看着整齐的是精简指令集, 如riscv, alpha, arm, mips

  7. 复杂指令集: 指令集格式看着不那么整齐的是复杂指令集, 如x86

  8. 生态: 生存状态, x86的电脑到处都是, 软件数不胜数, 生态强大, sw64用于超算, 软件很少. 在计算机方面生态可以指软件的数量. 两大生态联盟: Windows+Intel, 简称Wintel体系, Android+Arm, 简称AA体系.

git

你可以不会linux, 但你必须要会git -- 佚名

git分支操作的诱人之处

graph LR
A -->|提供一份代码X| B
B -->|增加补丁b| A
A -->|提供相同代码X| C
C -->|增加补丁c| A

补丁之间关联, 现在A要合并补丁b和补丁c, 该怎么操作?

直白的:

git am b
git am c

不推荐的:

git am b
patch -p1 < c
fix reject
git add .....
git commit ....

推荐的:

git am b
git apply c --reject
fix reject
git add .....
git commit ....

git 流程:

---
title: 常规操作
---
gitGraph
   commit id: "a"
   commit id: "b"
   commit id: "c"

挑着合入的:

git am b
git checkout HEAD~1 -b tmp
git switch tmp
git am c
git switch master
git cherry-pick tmp~0
fix reject
git add -u
git cherry-pick --continue

git 流程:

---
title: 补丁提取
---
gitGraph
   commit id:"a"
   branch tmp
   commit id:"c"
   checkout main
   commit id:"b"
   cherry-pick id:"c"

全部合入的:

git am b
git checkout HEAD~1 -b tmp
git switch tmp
git am c
git switch master
git merge tmp
fix reject
git add -u
git merge --continue

git 流程:

---
title: 补丁合并
---
gitGraph
   commit id:"a"
   branch tmp
   commit id:"c"
   checkout main
   commit id:"b"
   merge tmp id:"mergeC"

变基合入的:

git am b
git checkout HEAD~1 -b tmp
git switch tmp
git am c
git switch master
git rebase master tmp
fix reject
git add -u
git rebase --continue
git switch master
git merge tmp

git 流程:

---
title: 补丁合并
---
gitGraph
   commit id:"a"
   branch tmp
   commit id:"c"
   checkout main
   commit id:"b"
   checkout tmp
   merge main id:"rebase"
   checkout main
   merge tmp id:"MergeRebase"

上述操作只演示了一个目标分支上两个补丁之间的关系, 有些操作显得华而不实, 但是倘若分支数量和补丁数量都翻一倍呢?

假定有以下场景:

graph LR
A -->|增加补丁x| X
A -->|增加补丁y| Y
A -->|增加补丁z| Z
A -->|增加补丁m| M
A -->|增加补丁n| N
A -->|增加补丁m和补丁x| MX
A -->|增加补丁m和补丁n| MN
A -->|增加补丁y和补丁m和补丁z| YMZ
A -->|增加补丁x和补丁n| XN

这时, 每次提供代码时, 如果只在一个分支操作, 那就需要不停执行git reset和git am操作

如果这些需求是同一时间提出的, 那就需要复制多份文件夹, 每个文件夹起不同的名字, 然后再不同的文件夹中执行git reset和git am操作

再假如我们可以提前预知这些操作, 大致操作如下:

cp -rf A A-X
cp -rf A A-Y
cp -rf A A-Z
cp -rf A A-M
cp -rf A A-N
cp -rf A A-MX
cp -rf A A-MN
cp -rf A A-YMZ
cp -rf A A-XN
cd A-X
git am x
cd ../A-Y
git am y
cd ../A-Z
git am z
cd ../A-M
git am m
cd ../A-N
git am n
cd ../A-MX
git am m
git am x
cd ../A-MN
git am m
git am n
cd ../A-YMZ
git am y
git am m
git am z
cd ../A-XN
git am x
git am n

假如使用分支操作呢?

git branch X
git am x
git branch Y
git am y
git branch Z
git am z
git branch M
git am m
git branch N
git am n
git checkout M -b MX
git cherry-pcik X~0
git checkout M -b MN
git cherry-pcik N~0
git checkout Y -b YMZ
git cherry-pcik M~0
git cherry-pcik Z~0
git checkout X -b XN
git cherry-pcik N~0

git流程如下:

---
title: 基于分支提供代码
---
gitGraph
   commit id:"a"
   branch X
   branch Y
   branch Z
   branch M
   branch N
   checkout X
   commit id:"x"
   branch XN
   checkout Y
   commit id:"y"
   checkout Z
   commit id:"z"
   checkout M
   commit id:"m"
   
   branch MX
   branch MN
   branch YMZ
   
   checkout N
   commit id:"n"

   checkout MX
   merge X id:"mx"
   checkout MN
   merge N id:"mn"
   checkout YMZ
   merge Y id:"ym"
   merge Z id:"ymz"
   checkout XN
   merge N id:"xn"

此外, 再假如, YMZ补丁对应客户, 发现m补丁有问题, 需要不停调试, 同事Y和Z不用变化, 此时可以想一想, 复制文件夹和分支操作, 哪一种效率高, 可维护性强.

远程分支问题

git的一大亮点在于远程分支协同开发. 与其他开发者进行补丁交流是一大重点. 以下分享一些实用的Tips.

1. 下载补丁

  • Branch, 获取补丁后, 直接新建分支, 特性在于switch回原分支的时候, 该分支仍然存在, 后面需要用还能用.

  • Checkout, 获取补丁后, 处于分离的分支, 特性在于switch回原分支的时候, 该分支就不存在了, 如果只是看代码, 或者临时编译尝试功能时, 非常适合使用.

  • Cherry Pick, 获取补丁后, 基于当前补丁, 应用补丁, 不会切换分支, 相较于git am, 它是基于当前补丁, 只要不冲突就能直接打上, 而git am是基于补丁的父补丁, 补丁不冲突的时候可能也会多出一条merge信息(跟git服务相关).

  • Format Patch, 把补丁的内容作为标准输出, 并不应用补丁, 可以据此重定向为一个标准补丁再应用.

  • Pull, 会把补丁pull下来, 如果有冲突, 中间会出现merge信息, 如果没冲突, 会直接应用该补丁.

2. 分支拉取及推送

git pull remote-url remoteBranch:localBranch
git push remote-url localBranch:remoteBranch
git push --delete remoteBranch

3. pr和mr

目前贡献代码有两种方式:

  1. fork源仓库, 自己开发完了以后, 提交pr请求源仓库合入
  2. 创建分支, 自己开发完以后, 提交到自己的分支上, 提交mr请求主分支合入

github上通常使用第一种, gitlab通常使用第二种.

gerrit属于第二种

4. 实战思路分析

---
title: 一份代码同时用于生产, 开发, 不同类型的测试
---
gitGraph
   commit id:"a"
   branch out1
   branch out2
   branch out3
   branch upstream
   branch dev
   checkout out2
   commit id:"y"
   checkout out3
   commit id:"z"
   checkout upstream
   commit id:"fix bug"
   commit id:"add func"
   commit id:"optimze func"
   checkout dev
   commit id:"My func"
   checkout main
   merge upstream
   checkout out1
   commit id:"x"
   checkout out1
   cherry-pick id:"add func"
   cherry-pick id:"My func"
   checkout main
   cherry-pick id:"My func"

main分支用于提交代码

out分支用于向不同客户提供软件

upstream分支用于时时更新, 便于main提交时rebase到最新

dev分支用于本地开发

其他git操作推荐

  1. git cherry-pick
  2. git checkout -
  3. git stash
  4. git branch -m/-D
  5. git log --oneline, git log --author="Ayden" --since="2022-01-01" --util="2024-01-01"
  6. git remote -v ,   git remote add ,  git remote remove
  7. git pull origin remote-brach:local-branch 8. git push origin local-branch:remote-brach
  8. git merge branchX
  9. git cherry-pick branchX~N
  10. git reflog
  11. git rebase -i
  12. git config
  13. git rm
  14. git clean -xdf
  15. git add -u
  16. git checkout --orphan branch
  17. git diff --cached
  18. git apply --reject
  19. git log -p
  20. git blame
  21. git checkout commit file
  22. git diff commit1 commit2

linux

常见的shell操作要会:

  1. 硬件信息查看相关命令: lspci, lsusb, lsblk, fdisk, hwclock, sensors, busybox, dmidecode, lscpu,  df, dh

  2. 系统信息查看相关命令: ntpdate, hwclock, date, uname, lsmod, modprobe, dmesg,  df, dh, free, top, htop, ps, iostat

  3. 字符串处理相关命令: sed, awk, grep, cut, tr, tee, diff, uniq, td

  4. 自动化交互相关命令: expect-spawn, read, sshpass, 重定向

  5. 系统服务管理相关命令: dhclient, systemctl,  su, journalctl, kill, jobs, service, ps, netstat, fuser, iostat

  6. 磁盘文件等相关命令: mkfs, mount, md5sum, fdisk, gpart, parted, file, mkswap, swapon, smartctl, fsck, xfs_repair

  7. 常用系统服务: ssh, dhcp, mount, ntp, ftp, http, nfs

  8. 常用接口测试命令: arecord, cvlc, wget, aplay, x11perf, iperf3, glmark2, fio, ip, ifconfig, ethtools

  9. 文本处理相关工具: nano, vi, vim, gedit, emacs, cat, diff

  10. 一些好用的循环处理工具及其他: watch, timeout, time, loop, seq, xargs

然后加以组合, 灵活运用

实战案例:

比如需要找到一个目录中不是文件夹的文件:

tree . | sed 's/\S* //g' | sed '$ d' | grep -v "/" | xargs -I N find . -name N | xargs -I N file N | grep -vw "directory$" | awk -F ':' '{print $1}'

比如想要列出所有的磁盘设备:

lsblk -P | grep "TYPE=\"disk\"" | awk -F '"' '{print $2}'

灵活运用即可.

C语言

点击此处穿越回大学

学完C, 记得了解一下Makefile, 链接脚本等内容

debug

  1. 打印调试法
  2. gdb调试法
  3. 肉眼调试法

gdb

例如有如下C代码:

#include <stdio.h>
#include <stdlib.h>

static int si = 123;

int compare(int a, int b)
{
	return (a >= b ? 1 : 0);
}

void info()
{
	printf("a >= b!\n");
}

void main()
{
	int i = 321;
	if (!compare(si, i)) {
		printf("a < b!\n");
	}
}

先编译:

gcc -g a.c -o a

再使用gdb调试:

$ gdb
GNU gdb (GDB) 13.2
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "loongarch64-unknown-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) file a
Reading symbols from a...
(gdb) list
4	static int si = 123;
5	
6	int compare(int a, int b)
7	{
8		return (a >= b ? 1 : 0);
9	}
10	
11	void info()
12	{
13		printf("a >= b!\n");
(gdb) 
14	}
15	
16	void main()
17	{
18		int i = 321;
19		if (!compare(si, i)) {
20			printf("a < b!\n");
21		}
22	}
(gdb) b 19
Breakpoint 1 at 0x810: file a.c, line 19.
(gdb) r
Starting program: /tmp/a/a 

Breakpoint 1, main () at a.c:19
19		if (!compare(si, i)) {
(gdb) p !compare(si, i)
$1 = 1
(gdb) p si
$2 = 123
(gdb) p i
$3 = 321
(gdb) disassemble 
Dump of assembler code for function main:
   0x00005555555547f8 <+0>:	addi.d      	$sp, $sp, -32(0xfe0)
   0x00005555555547fc <+4>:	st.d        	$ra, $sp, 24(0x18)
   0x0000555555554800 <+8>:	st.d        	$fp, $sp, 16(0x10)
   0x0000555555554804 <+12>:	addi.d      	$fp, $sp, 32(0x20)
   0x0000555555554808 <+16>:	addi.w      	$t0, $zero, 321(0x141)
   0x000055555555480c <+20>:	st.w        	$t0, $fp, -20(0xfec)
=> 0x0000555555554810 <+24>:	pcalau12i   	$t0, 8(0x8)
   0x0000555555554814 <+28>:	ld.w        	$t0, $t0, 0
   0x0000555555554818 <+32>:	ldptr.w     	$t1, $fp, -20(0xffec)
   0x000055555555481c <+36>:	move        	$a1, $t1
   0x0000555555554820 <+40>:	move        	$a0, $t0
   0x0000555555554824 <+44>:	bl          	-172(0xfffff54)	# 0x555555554778 <compare>
   0x0000555555554828 <+48>:	move        	$t0, $a0
   0x000055555555482c <+52>:	bnez        	$t0, 16(0x10)	# 0x55555555483c <main+68>
   0x0000555555554830 <+56>:	pcalau12i   	$t0, 1(0x1)
   0x0000555555554834 <+60>:	addi.d      	$a0, $t0, -1952(0x860)
   0x0000555555554838 <+64>:	bl          	-616(0xffffd98)	# 0x5555555545d0 <puts@plt>
   0x000055555555483c <+68>:	andi        	$zero, $zero, 0x0
   0x0000555555554840 <+72>:	ld.d        	$ra, $sp, 24(0x18)
   0x0000555555554844 <+76>:	ld.d        	$fp, $sp, 16(0x10)
   0x0000555555554848 <+80>:	addi.d      	$sp, $sp, 32(0x20)
   0x000055555555484c <+84>:	jirl        	$zero, $ra, 0
End of assembler dump.
(gdb) ni
0x0000555555554814	19		if (!compare(si, i)) {
(gdb) ni
0x0000555555554818	19		if (!compare(si, i)) {
(gdb) ni
0x000055555555481c	19		if (!compare(si, i)) {
(gdb) ni
0x0000555555554820	19		if (!compare(si, i)) {
(gdb) ni
0x0000555555554824	19		if (!compare(si, i)) {
(gdb) ni
0x0000555555554828	19		if (!compare(si, i)) {
(gdb) ni
0x000055555555482c	19		if (!compare(si, i)) {
(gdb) ni
20			printf("a < b!\n");
(gdb) bt
#0  main () at a.c:20
(gdb) si
0x0000555555554834	20			printf("a < b!\n");
(gdb) si
0x0000555555554838	20			printf("a < b!\n");
(gdb) si
0x00005555555545d0 in puts@plt ()
(gdb) si
0x00005555555545d4 in puts@plt ()
(gdb) c
Continuing.
a < b!
[Inferior 1 (process 277277) exited with code 07]

可以断点执行, 反汇编, 单步执行, 时时查看变量值, 非常方便.

gdbserver

在目标机器执行:

gdbserver :1235 a

在远程机器执行:

(gdb) target remote 192.168.1.4:12345

通过此方法, 在嵌入式领域, 通过硬件调试器, 可以实现远程调试elf文件.

肉眼调试法--gcc工具链

除了上面的调试法, 还有一些信息需要指导, 当没有自动化的debug环境时, 可以通过自行反汇编, 对比代码执行流程, 比如objdump和nm工具:

objdump:

$ objdump -D a | head -20

a:     文件格式 elf64-loongarch


Disassembly of section .interp:

0000000000000238 <.interp>:
 238:	62696c2f 	blt         	$ra, $t3, -104084	# fffffffffffe6ba4 <_end+0xfffffffffffdeb44>
 23c:	6c2f3436 	bgeu        	$ra, $fp, 12084	# 3170 <__GNU_EH_FRAME_HDR+0x2908>
 240:	696c2d64 	bltu        	$a7, $a0, 93228	# 16e6c <_end+0xee0c>
 244:	2d78756e 	.word		0x2d78756e
 248:	6e6f6f6c 	bgeu        	$s4, $t0, -102548	# fffffffffffe71b4 <_end+0xfffffffffffdf154>
 24c:	63726167 	blt         	$a7, $a3, -36256	# ffffffffffff74ac <_end+0xfffffffffffef44c>
 250:	706c2d68 	.word		0x706c2d68
 254:	2e643436 	ldr.w       	$fp, $ra, -1779
 258:	312e6f73 	vstelm.w    	$vr19, $s4, -404, 0x3
	...

Disassembly of section .note.gnu.build-id:

nm:

$ nm a | sort 
0000000000000590 a _PROCEDURE_LINKAGE_TABLE_
00000000000005e0 T _start
0000000000000640 t deregister_tm_clones
00000000000006a0 t register_tm_clones
0000000000000700 t __do_global_dtors_aux
0000000000000760 t frame_dummy
0000000000000778 T compare
00000000000007c8 T info
00000000000007f8 T main
0000000000000850 R _IO_stdin_used
0000000000000868 r __GNU_EH_FRAME_HDR
0000000000007e40 a _DYNAMIC
0000000000008000 d si
0000000000008030 a _GLOBAL_OFFSET_TABLE_
0000000000008030 d __TMC_END__
0000000000008050 d __dso_handle
0000000000008058 B __bss_start
0000000000008058 b completed.0
0000000000008058 D _edata
0000000000008060 B _end
                 U abort@GLIBC_2.36
                 U __libc_start_main@GLIBC_2.36
                 U puts@GLIBC_2.36
                 w __cxa_finalize@GLIBC_2.36
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable

相关工具及教程非常多, 不再演示.

图吧垃圾佬

主板, CPU, 电源, 风扇, 内存条, PCIE设备, sata, hdd, nvme, m.2, uart, vga, hdmi, flash, 网口, 蓝牙, usb口, usb扩展线, hda接口, ps/2接口, 等名称和图片应该能对的上.

显示器和主机能区分. 不会把电源线插vga口里就行

其他好用工具

vim

vim是编辑器, 没有ide那么方便, 但是用熟了之后, 很多操作可以比ide更顺手.

比如部分代码格式化, 对比ide, 比如idea, idea需要鼠标选中, 然后按ctrl-alt-L

vim可以通过shift-v再按代码行数和回车键完成选中, 以此替代idea的鼠标选中过程, 然后按=实现格式化.

对比起来, vim使用按键选中代码的方式看起来更复杂, 但是对于文件中部分上百行代码来说, 会更轻松一些.

另外vim支持很多插件, 可以大大提高办公效率, 简做推荐:

  1. chxuan/vim-buffer 可以切换不同的文件buffer, 即文件历史管理及查看.
  2. preservim/tagbar 显示文件内的全部函数
  3. Yggdroot/LeaderF 模糊匹配文件
  4. preservim/nerdtree 文件树
  5. Valloric/YouCompleteMe C语言补全神器, 更推荐使用neoclide/coc.nvim, 各种代码都能补全
  6. preservim/nerdcommenter 一键注释

还有诸多好用插件, 可以自行发现.

regex

正则表达式可以有效加快你处理大量字符串的速度, 比如下面的处理可以将pcie的链接速率干干净净的打印出来.

lspci -vvv -s 00:09.0 | grep "\<LnkSta\>" | sed 's/.*\(Speed.*GT\/s\).*/\1/g' | awk '{print $2}'

pushd and popd

通常我们使用cdcd -去进行两个目录切换, 但是当遇到更多目录时, 可以通过pushedpopd在多个目录中快速切换.

仔细体会下面进行目录切换的操作:

mxd@mxd:~$ pushd gitrepo/
~/gitrepo ~
mxd@mxd:~/gitrepo$ pushd linux/
~/gitrepo/linux ~/gitrepo ~
mxd@mxd:~/gitrepo/linux$ pushd block/partitions/
~/gitrepo/linux/block/partitions ~/gitrepo/linux ~/gitrepo ~
mxd@mxd:~/gitrepo/linux/block/partitions$ dirs -v
 0  ~/gitrepo/linux/block/partitions
 1  ~/gitrepo/linux
 2  ~/gitrepo
 3  ~
mxd@mxd:~/gitrepo/linux/block/partitions$ pushd +2
~/gitrepo ~ ~/gitrepo/linux/block/partitions ~/gitrepo/linux
mxd@mxd:~/gitrepo$ dirs -v
 0  ~/gitrepo
 1  ~
 2  ~/gitrepo/linux/block/partitions
 3  ~/gitrepo/linux
mxd@mxd:~/gitrepo$ pushd +3
~/gitrepo/linux ~/gitrepo ~ ~/gitrepo/linux/block/partitions
mxd@mxd:~/gitrepo/linux$ 
mxd@mxd:~/gitrepo/linux$ dirs -v
 0  ~/gitrepo/linux
 1  ~/gitrepo
 2  ~
 3  ~/gitrepo/linux/block/partitions
mxd@mxd:~/gitrepo/linux$ popd +2
~/gitrepo/linux ~/gitrepo ~/gitrepo/linux/block/partitions
mxd@mxd:~/gitrepo/linux$ dirs -v
 0  ~/gitrepo/linux
 1  ~/gitrepo
 2  ~/gitrepo/linux/block/partitions
mxd@mxd:~/gitrepo/linux$ pushd +2
~/gitrepo/linux/block/partitions ~/gitrepo/linux ~/gitrepo
mxd@mxd:~/gitrepo/linux/block/partitions$ 

cscope and ctags

cscope比ctags能够查询的方式更多, 但也稍微复杂, 不过可以通过在vimrc中配置快捷键解决:

nmap <C-\>s :cs find s <C-R>=expand("<cword>")<CR><CR>
nmap <C-\>g :cs find g <C-R>=expand("<cword>")<CR><CR>
nmap <C-\>c :cs find c <C-R>=expand("<cword>")<CR><CR>
nmap <C-\>t :cs find t <C-R>=expand("<cword>")<CR><CR>   
nmap <C-\>e :cs find e <C-R>=expand("<cword>")<CR><CR>
nmap <C-\>f :cs find f <C-R>=expand("<cfile>")<CR><CR>
nmap <C-\>i :cs find i ^<C-R>=expand("<cfile>")<CR>$<CR>
nmap <C-\>d :cs find d <C-R>=expand("<cword>")<CR><CR>

详情必应.

AI

21世纪要学会善用工具, 比如同样搜索海鲜和维生素c一起吃与生成砒霜的量的关系海鲜 维生素 混吃 砒霜 量化关系

可以自行对比AI和浏览器返回的信息差异.

个人认为, AI是在分析人类表达的基础上做出来的, 只要你的语句表达的符合语法, 基本上可以准确无误的去给你你想要的结果.

而浏览器为了准确查询, 是在解析人类提供的关键词, 所以问题中的关键词要提供充足且准确, 才能查到想要的结果.

以下做一些工具推荐:

AI:

字节-豆包: www.doubao.com/chat/?login…

科大讯飞-星火: xinghuo.xfyun.cn/desk

阿里-通义千问: tongyi.aliyun.com/qianwen

华为-盘古: pangu.huaweicloud.com/index.html?…

百度-文心一言: yiyan.baidu.com/

微软-NewBing(Copilot): copilot.microsoft.com/

chatgpt: chat.openai.com/

谷歌-gemini: gemini.google.com/?hl=en

搜索引擎:

必应: www.bing.com/

搜狗: www.sogou.com/

谷歌: www.google.com

技巧:

浏览器搜索多用关键词和空格

AI搜索多检查语法, 描述清晰