openbmc dbus架构简析(二)
1.说明
以前看内核代码觉得难,是因为内核代码涉及到硬件原理与算法结构和层次递进的代码逻辑,现在的应用层因为业务的复杂与代码和内核的交互接口复杂,也变得有些难度了。
这篇文章是继:openbmc dbus架构简析的第二篇文章。
首先贴出来前篇文章的图,与简述内容:
* 1.inherit_fds()使用systemd机制获取到socket描述符(请先了解systemd的socket机制原理,会先接手socket服务,accept默认为no,因此需要sd_listen_fds()方法获取socket描述符)
* 2.父子进程通过socketpair(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, controller);中的controller[0]和controller[1]进行连接
* 3.sd_bus_set_fd(launcher->bus_controller, controller[0], controller[0]);使用该函数,实际bus->input_fd = input_fd;bus->output_fd = output_fd; 实际即为controller[0]
* 4.sd_bus_call(launcher->bus_controller, m, 0, NULL, NULL);发送socket数据实际依靠的是controller[0],而子进程使用的是controller[1],通道打通,数据可以传递给子进程
* 5.sd_bus_message_append(m, "oh","/org/bus1/DBus/Listener/0",launcher->fd_listen);把launcher->fd_listen 为总体的systemd的监听的socket描述符传递给了子进程。

2.代码分析
2.1 代码使用的描述符
2.1.1 dbus-broker-launch使用的描述符
根据代码,列出来使用到的描述符:
static int run(void)
---> launcher_new()---> r = launcher_open_log(launcher);---> fd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); //新建了描述符, fd=4---> sd_event_default(&launcher->event);--->...---> e->epoll_fd = epoll_create1(EPOLL_CLOEXEC); //创建了描述, epoll_fd=5.---> r = sd_event_add_signal(launcher->event, NULL, SIGTERM, NULL, NULL);---> r = sd_event_add_signal(launcher->event, NULL, SIGTERM, NULL, NULL);---> r = sd_event_add_signal(launcher->event, NULL, SIGTERM, NULL, NULL);---> r = event_make_signal_data(e, sig, &d);---> r = signalfd(d->fd >= 0 ? d->fd : -1,...) 使用了signalfd创建的fd=6
---> r = launcher_run(launcher);---> r = launcher_parse_config(launcher, &root, &nss_cache);---> r = dirwatch_new(&dirwatch); ---> dw->inotify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); //新建fd=7---> r = config_parser_read(&parser, rootp, configfile, nss_cache, dirwatch);---> r = config_parser_include(parser, root, node, nss_cache, dirwatch);---> _c_cleanup_(c_closep) int fd = -1;---> r = open(node->include.file->path, O_RDONLY | O_CLOEXEC | O_NOCTTY);---> ... //这里 fd=8, 实际需要调用close() ? 使用_c_cleanup_语法实际关闭了---> r = socketpair(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, controller);---> ...返回了2个套接字 分别为 controller[0] = 8 和 controller[0] = 9
如下图,可以看到基本的文件描述符均为默认状态:

参考文档: https://www.jinbuguo.com/systemd/systemd.exec.html#, 描述了如下内容:



因此,fd=0,fd=1,fd=2的结果如下:

因此,对于openbmc,常见的printf,fprintf(stderr…)均会输出到journal中。
- 对于
fd=3继承于systemd,接收外部的服务。 - 对于
fd=4创建socket,用来将数据传递到:/run/systemd/journal/socket - 对于
fd=5, 创建e->epoll_fd - 对于
fd=6,使用signalfd()创建fd - 对于
fd=7,使用inotify_init1()创建fd - 对于
fd=8,fd=9,使用socketpair()创建fd,用来父子进程数据通信
再回到开头的那副图,可以看看子进程与父进程之间,父进程通过sd-bus接口调用将fd(systemd监控转储给dbus-broker的socket)描述符传递给子进程。

继续分析文件:src\launch\launcher.c:
static int launcher_add_listener(Launcher *launcher, Policy *policy,...)
---> r = sd_bus_message_new_method_call(launcher->bus_controller, &m,NULL,"/org/bus1/DBus/Broker","org.bus1.DBus.Broker","AddListener");---> _cleanup_(sd_bus_message_unrefp) sd_bus_message *t = NULL;---> r = sd_bus_message_new(bus, &t, SD_BUS_MESSAGE_METHOD_CALL);---> t->header->endian = BUS_NATIVE_ENDIAN;---> t->header->type = type;---> t->header->version = bus->message_version;---> t->allow_fds = bus->can_fds || !IN_SET(bus->state, BUS_HELLO, BUS_RUNNING);
---> r = sd_bus_message_append(m, "oh","/org/bus1/DBus/Listener/0",launcher->fd_listen);
---> r = policy_export(policy, m, system_console_users, n_system_console_users);
---> r = sd_bus_call(launcher->bus_controller, m, 0, NULL, NULL);
实际上,这里面的重点是函数:sd_bus_call(),在文件:src\libsystemd\sd-bus\sd-bus.c中:
int sd_bus_call(...)
---> bus_write_message()---> bus_socket_write_message()---> if (m->n_fds > 0 && *idx == 0)---> mh.msg_controllen = CMSG_SPACE(sizeof(int) * m->n_fds);---> mh.msg_control = alloca0(mh.msg_controllen);---> control = CMSG_FIRSTHDR(&mh);---> control->cmsg_len = CMSG_LEN(sizeof(int) * m->n_fds);---> control->cmsg_level = SOL_SOCKET;---> control->cmsg_type = SCM_RIGHTS;---> memcpy(CMSG_DATA(control), m->fds, sizeof(int) * m->n_fds);---> k = sendmsg(bus->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
使用如上调用步骤传递文件描述符给另外一个进程,使用的方法是父子进程之间通过sendmsg()调用。
继续查看函数sd_bus_message_append()的调用关系:
int sd_bus_message_append(sd_bus_message *m, const char *types, ...)
---> r = sd_bus_message_appendv(m, types, ap); //type是字符串"oh"
---> switch (*t)---> case SD_BUS_TYPE_UNIX_FD:---> uint32_t x;---> x = va_arg(ap, uint32_t);---> r = sd_bus_message_append_basic(m, *t, &x);---> u32 = m->n_fds;---> case SD_BUS_TYPE_OBJECT_PATH:---> const char *x;---> x = va_arg(ap, const char*);---> r = sd_bus_message_append_basic(m, *t, x);
这些sd-bus类型有必要列出来,在文件:src\systemd\sd-bus-protocol.h中:
enum {_SD_BUS_TYPE_INVALID = 0,SD_BUS_TYPE_BYTE = 'y',SD_BUS_TYPE_BOOLEAN = 'b',SD_BUS_TYPE_INT16 = 'n',SD_BUS_TYPE_UINT16 = 'q',SD_BUS_TYPE_INT32 = 'i',SD_BUS_TYPE_UINT32 = 'u',SD_BUS_TYPE_INT64 = 'x',SD_BUS_TYPE_UINT64 = 't',SD_BUS_TYPE_DOUBLE = 'd',SD_BUS_TYPE_STRING = 's',SD_BUS_TYPE_OBJECT_PATH = 'o',SD_BUS_TYPE_SIGNATURE = 'g',SD_BUS_TYPE_UNIX_FD = 'h',SD_BUS_TYPE_ARRAY = 'a',SD_BUS_TYPE_VARIANT = 'v',SD_BUS_TYPE_STRUCT = 'r', /* not actually used in signatures */SD_BUS_TYPE_STRUCT_BEGIN = '(',SD_BUS_TYPE_STRUCT_END = ')',SD_BUS_TYPE_DICT_ENTRY = 'e', /* not actually used in signatures */SD_BUS_TYPE_DICT_ENTRY_BEGIN = '{',SD_BUS_TYPE_DICT_ENTRY_END = '}'
};
在文件:src\broker\controller.c中,函数:controller_init()调用:
int controller_init(Controller *c, Broker *broker, int controller_fd)
---> r = connection_init_server(&controller->connection,&broker->dispatcher,controller_dispatch_connection,broker->bus.user,"0123456789abcdef",controller_fd);
其中,函数controller_dispatch_connection()定义:
static int controller_dispatch_connection(DispatchFile *file)
---> r = connection_dispatch(&controller->connection, dispatch_file_events(file));
---> r = connection_dequeue(&controller->connection, &m);---> r = socket_dequeue(&connection->socket, &message);---> r = message_new_incoming(&message, socket->in.header);
---> r = message_parse_metadata(m);---> r = message_parse_header(message, &message->metadata);---> switch (field)---> case DBUS_MESSAGE_FIELD_PATH:---> case DBUS_MESSAGE_FIELD_INTERFACE:---> case DBUS_MESSAGE_FIELD_MEMBER:---> case DBUS_MESSAGE_FIELD_REPLY_SERIAL:---> case DBUS_MESSAGE_FIELD_UNIX_FDS:---> c_dvar_read(&v, "<u>)", c_dvar_type_u, &metadata->fields.unix_fds);---> r = message_parse_body(message, &message->metadata);
---> r = controller_dbus_dispatch(controller, m);---> switch (message->header->type) {---> case DBUS_MESSAGE_TYPE_METHOD_CALL:---> r = controller_dispatch_object(controller,message_read_serial(message),message->metadata.fields.interface,message->metadata.fields.member,message->metadata.fields.path,message->metadata.fields.signature,message);---> if (strcmp(path, "/org/bus1/DBus/Broker") == 0)---> controller_dispatch_controller(controller, serial, member, path, signature, message);---> controller_method_add_listener( ... )---> listener_fd = fdlist_get(fds, fd_index);---> r = controller_add_listener(controller, &listener, path, listener_fd, policy);---> listener_init_with_fd(...)---> listener->bus = bus;---> dispatch_file_init(.., listener_dispatch,... )
这里面的判断:message->header->type来自于src\launch\launcher.c的sd_bus_message_new_method_call( )
最后,调用到函数listener_dispatch():
static int listener_dispatch(DispatchFile *file)
---> fd = accept4(listener->socket_fd, NULL, NULL, SOCK_CLOEXEC | SOCK_NONBLOCK);
2.1.2 socket takeover
需要首先查阅该篇文章: Zero Downtime Release: Disruption-free Load Balancing of a Multi-Billion User Website.
可参考文件src\util\log.c中的函数log_fd_send():
static int log_fd_send(int destination_fd, int payload_fd)
---> control.cmsg.cmsg_level = SOL_SOCKET;
---> control.cmsg.cmsg_type = SCM_RIGHTS;
---> l = sendmsg(destination_fd, &msg, MSG_NOSIGNAL);
2.1.3 描述符附录
简单看一下应用程序使用的描述符:

root@evb-ast2500:~# ps | grep -i "dbus"191 messageb 5836 S /usr/bin/dbus-broker-launch --scope system --audit192 messageb 2756 S dbus-broker --log 4 --controller 9 --machine-id c47c0d3d042848a1908818ca62f0644e --max-bytes 536870912 --max-fds 4096 --max-matches 16384 --audit344 root 2952 S grep -i dbus
root@evb-ast2500:~#
root@evb-ast2500:~# ls -al /proc/191/fd
dr-x------ 2 root root 13 Feb 27 17:54 .
dr-xr-xr-x 8 messageb messageb 0 Feb 27 17:54 ..
lr-x------ 1 root root 64 Feb 27 17:54 0 -> /dev/null
lrwx------ 1 root root 64 Feb 27 17:54 1 -> socket:[2242]
lrwx------ 1 root root 64 Feb 27 17:55 10 -> anon_inode:[pidfd]
lrwx------ 1 root root 64 Feb 27 17:55 11 -> anon_inode:[timerfd]
lrwx------ 1 root root 64 Feb 27 17:55 12 -> /memfd:dbus-broker-log (deleted)
lrwx------ 1 root root 64 Feb 27 17:54 2 -> socket:[2242]
lrwx------ 1 root root 64 Feb 27 17:54 3 -> socket:[2210]
lrwx------ 1 root root 64 Feb 27 17:54 4 -> socket:[2246]
lrwx------ 1 root root 64 Feb 27 17:55 5 -> anon_inode:[eventpoll]
lrwx------ 1 root root 64 Feb 27 17:55 6 -> anon_inode:[signalfd]
lr-x------ 1 root root 64 Feb 27 17:55 7 -> anon_inode:inotify
lrwx------ 1 root root 64 Feb 27 17:55 8 -> socket:[2254]
lrwx------ 1 root root 64 Feb 27 17:54 9 -> socket:[2303]
root@evb-ast2500:~#
2.2 bus的建立
2.2.1 launcher bus
在文件:src\launch\launcher.c中调用如下函数launcher_new():
int launcher_new(Launcher **launcherp, int fd_listen, bool audit, const char *configfile, bool user_scope)
---> r = sd_bus_new(&launcher->bus_controller);
另外函数:launcher_run()调用如下:
int launcher_run(Launcher *launcher)
---> r = sd_bus_set_fd(launcher->bus_controller, controller[0], controller[0]);
---> r = sd_bus_start(launcher->bus_controller);---> bus_set_state(bus, BUS_OPENING);---> if (bus->input_fd >= 0) ---> r = bus_start_fd(bus);---> return bus_send_hello(bus);//直接返回,并未调用"hello"
由于开启的服务是:

在函数:launcher_connect()中:
static int launcher_connect(Launcher *launcher)
---> r = sd_bus_open_system(&launcher->bus_regular);---> sd_bus_open_system_with_description(ret, NULL);---> r = sd_bus_new(&b);---> r = bus_set_address_system(b);---> b->bus_client = true;---> b->is_local = true;---> r = sd_bus_start(b);---> bus_set_state(bus, BUS_OPENING);---> r = bus_start_address(bus);---> return bus_send_hello(bus);---> r = sd_bus_message_new_method_call(bus, &m,"org.freedesktop.DBus","/org/freedesktop/DBus","org.freedesktop.DBus","Hello");---> sd_bus_call_async(bus, NULL, m, hello_callback, NULL, 0);
设置系统默认的bus:"unix:path=/run/dbus/system_bus_socket".
在如下调用关系中:
static int listener_dispatch(DispatchFile *file)
---> ...
---> r = peer_dispatch(&peer->connection.socket_file);---> r = peer_dispatch_connection(peer, dispatch_file_events(file) & interest[i]);---> r = connection_dispatch(&peer->connection, events);---> r = connection_dequeue(&peer->connection, &m);---> r = message_parse_metadata(m);---> r = driver_dispatch(peer, m);---> r = driver_dispatch_internal(peer, message);---> if (string_equal(message->metadata.fields.destination, "org.freedesktop.DBus"))---> driver_dispatch_interface(peer,...) ---> static const DriverInterface interfaces[] = {---> { "org.freedesktop.DBus", driver_methods },---> { "org.freedesktop.DBus.Monitoring", monitoring_methods },---> ... ---> }
其中,函数:driver_methods():
static const DriverMethod driver_methods[] = {{ "Hello", false, NULL, driver_method_hello, ...}static int driver_method_hello(Peer *peer, const char *path, CDVar *in_v, uint32_t serial, CDVar *out_v)
---> peer_register(peer);---> peer->registered = true;
---> unique_name = address_to_string(&(Address)ADDRESS_INIT_ID(peer->id)); //获取unique name
---> c_dvar_write(out_v, "(s)", unique_name); //将unique name返回
---> r = driver_send_reply(peer, out_v, serial);---> driver_send_reply_with_fds(peer, var, serial, NULL, 0);---> r = message_new_outgoing(&message, data, n_data);---> r = driver_send_unicast(peer, message);
---> r = driver_name_owner_changed(peer->bus, &peer->name_owner_changed_matches, NULL, NULL, peer);---> r = driver_notify_name_owner_changed(bus, matches, name, old_owner_str, new_owner_str);---> .fields = {---> .path = "/org/freedesktop/DBus",---> .interface = "org.freedesktop.DBus",---> .member = "NameOwnerChanged",---> }

3.分析一下权威代码
3.1 LogControl
可以查看网站:https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.LogControl1.html 或者systemd的代码: man\logcontrol-example.c.
注意:此处是建立一个server
int main(int argc, char **argv)
---> r = sd_bus_default(&bus);---> b->bus_client = true;
---> r = sd_bus_add_object_vtable(bus, NULL,"/org/freedesktop/LogControl1","org.freedesktop.LogControl1",vtable,&o);
---> r = sd_bus_request_name(bus, "org.freedesktop.Example", 0);---> r = sd_bus_call_method(bus,"org.freedesktop.DBus","/org/freedesktop/DBus","org.freedesktop.DBus","RequestName",NULL,&reply,"su",name,param);---> r = sd_bus_message_read(reply, "u", &ret);
---> for()---> {---> r = sd_bus_wait(bus, UINT64_MAX);---> r = bus_poll(bus, false, timeout_usec);---> r = sd_bus_process(bus, NULL);---> bus_process_internal(bus, ret);---> }
另外,systemd的代码:src\busctl\busctl.c是客户端代码,查看如下命令:
* $ busctl --user set-property org.freedesktop.Example \* /org/freedesktop/LogControl1 \* org.freedesktop.LogControl1 \* LogLevel \* "s" debug
代码调用如下:
static int run(int argc, char *argv[])
---> busctl_main(argc, argv);---> r = acquire_bus(false, &bus);---> r = sd_bus_new(&bus);---> sd_bus_set_description(bus, "busctl");---> r = sd_bus_set_bus_client(bus, true);bus->bus_client = b;---> r = sd_bus_set_watch_bind(bus, arg_watch_bind);---> r = sd_bus_start(bus);---> bus_send_hello(bus);---> r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2],"org.freedesktop.DBus.Properties", "Set");---> r = sd_bus_message_append(m, "ss", argv[3], argv[4]);---> r = sd_bus_message_open_container(m, 'v', argv[5]);---> r = message_append_cmdline(m, argv[5], &passed_fdset, &p);---> r = sd_bus_message_close_container(m);---> r = sd_bus_call(bus, m, arg_timeout, &error, NULL);
4.TBD…
相关文章:
openbmc dbus架构简析(二)
1.说明 以前看内核代码觉得难,是因为内核代码涉及到硬件原理与算法结构和层次递进的代码逻辑,现在的应用层因为业务的复杂与代码和内核的交互接口复杂,也变得有些难度了。 这篇文章是继:openbmc dbus架构简析的第二篇文章。 首先贴出来前篇…...
【二分查找】Leetcode例题
【1】69. x 的平方根 - 力扣(LeetCode) 🍡解题思路:首先想到的是暴力查找,从1开始依次比较x与num*num的大小,然后找出满足num*num<x且(num1)*(num1)>x的num值;再来看看能不能优化一下&…...
gitlab配置调试minio
官方文档 rails console 调试 查看配置Settings.uploads.object_store加载minio clientrequire fog/awsfog_connection Fog::Storage.new(provider: AWS,aws_access_key_id: 你的MINIO_ACCESS_KEY,aws_secret_access_key: 你的MINIO_SECRET_KEY,region: <S3 region>,e…...
Vue实战技巧:如何展示附件(PDF、MP4、Excel、Zip等)并修改名称下载
大家好,今天给大家分享一篇关于在Vue项目中展示附件(PDF、MP4、Excel、Zip等)并修改名称下载的教程。在实际开发过程中,这个功能非常实用,下面我们就一起来学习一下。 一、准备工作 首先,确保你的项目中已经…...
AI证件照制作 API 对接说明
AI证件照制作 API 对接说明 本文将介绍一种 AI证件照制作 API 对接说明,它是可以通过输入人像照片URL以及自己喜欢的模板来制作各种风格的证件照。 接下来介绍下 AI证件照制作 API 的对接说明。 申请流程 要使用 API,需要先到 AI证件照制作 API?inv…...
Macos用brew安装Nodejs亲手教程
首先确保brew已安装,搜索node资源,命令如下: brew search nodejs 演示结果如下: 安装nodejs brew install node22 或 brew install node 出现如下界面 表示正在安装,安装成功后,提示如下信息࿱…...
Node.js 新手教程
1、nodejs简介 Node.js 是一个开源和跨平台的 JavaScript 运行时环境。它是几乎所有类型项目的流行工具! Node.js 在浏览器之外运行 V8 JavaScript 引擎(Google Chrome 的核心)。这使得 Node.js 的性能非常出色。 Node.js 应用程序在单个进…...
Latex转word(docx)或者说PDF转word 一个相对靠谱的方式
0. 前言 投文章过程中总会有各种各样的要求,其中提供word格式的手稿往往是令我头疼的一件事。尤其在多公式的文章中,其中公式转换是一个头疼的地方,还有很多图表,格式等等,想想就让人头疼欲裂。实践中摸索出一条相对靠…...
前端热门面试题目——React、Node
img 标签的 srcset 属性的作用 srcset 属性允许开发者为不同设备或分辨率提供多个图像选项,优化加载的图片以适应设备的屏幕大小和分辨率。这提高了性能和用户体验。 示例: <img src"default.jpg" srcset"small.jpg 480w, medium.j…...
Ansible自动化一键部署单节点集群架构
自动化部署利器:Ansible 一键部署脚本 在现代IT基础设施管理中,Ansible以其简洁、强大的自动化能力脱颖而出。以下是精心打造的Ansible自动化一键部署脚本,旨在简化部署流程,提升效率,确保一致性和可靠性。 通过这个…...
电脑插入耳机和音响,只显示一个播放设备
1. 控制面板-硬件和声音-Realtek高清音频-扬声器-设备高级设置-播放设备里选择使用前部和后部输出设备同时播放两种不同的音频流 在声音设置中就可以看到耳机播放选项...
家政小程序开发,打造便捷家政生活小程序
目前,随着社会人就老龄化和生活压力的加重,家政服务市场的需求正在不断上升,家政市场的规模也正在逐渐扩大,发展前景可观。 在市场快速发展的影响下,越来越多的企业开始进入到市场中,同时家政市场布局也发…...
tcpdump抓包wireshark分析
背景 分析特定协议的数据包,如 HTTP、DNS、TCP、UDP 等,诊断网络问题,例如连接故障、延迟和数据包丢失。 大概过程 1.安装tcpdump yum update yum install tcpdump2.抓包,从当前时间起,一小时后停止,…...
文件无法直接拖入zotero
解决方法:取消管理员权限打开zotero。 具体如下:右键zotero应用程序,打开属性,选择“兼容性”,点击底下的“更改所有用户的设置”,在弹出的框中取消“以管理员身份运行此程序”。如下所示:...
使用 useMemo 和 React.memo 优化 React 组件渲染
在 React 中,性能优化是一个重要的主题,特别是在复杂的组件树中。本文将演示如何在同一个父组件中使用 useMemo 和 React.memo 来优化子组件的渲染。 1. 组件结构 创建一个父组件,包含两个子组件: MemoChild:使用 R…...
ISAAC SIM踩坑记录--添加第三方3D场景
ISAAC SIM仿真首先就是要有合适的3D场景,官方提供了一些场景,如果不能满足要求,那就只能自己建。 对于我这种不会3D建模的菜鸟,只能到网上下载了,sketchfab就是一个不错的平台,有不少免费资源可以下载。 …...
Git 详解
Git 详解 Git 是一个分布式版本控制系统,用于高效地管理项目代码的版本历史。它是目前最流行的版本控制工具之一,广泛应用于软件开发领域。Git 的分布式架构允许开发者在本地进行代码的版本管理,并与远程仓库同步,实现团队协作。…...
Linux操作系统3-文件与IO操作1(从C语言IO操作到系统调用)
上篇文章:Linux操作系统2-进程控制3(进程替换,exec相关函数和系统调用)_execv系统调用-CSDN博客 本篇代码Gitee仓库:myLerningCode 橘子真甜/linux学习 - 码云 - 开源中国 (gitee.com) 本篇重点:C语言基础IO与系统调用 目录 一.…...
【Python网络爬虫笔记】8- (BeautifulSoup)抓取电影天堂2024年最新电影,并保存所有电影名称和链接
目录 一. BeautifulSoup的作用二. 核心方法介绍2.1 构造函数2.2 find()方法2.3 find_all()方法2.4 select()方法 三. 网络爬虫中使用BeautifulSoup四、案例爬取结果 一. BeautifulSoup的作用 解析HTML/XML文档:它可以将复杂的HTML或XML文本转换为易于操作的树形结构…...
Rancher V2.7.0安装教程
1、执行Docker命令 docker run -d --privileged --restartunless-stopped -p 80:80 -p 443:443 -v /home/rancher:/var/lib/rancher --name rancher registry.cn-hangzhou.aliyuncs.com/rancher/rancher:v2.7.0 注:如果容器启动失败,参考我另外一篇文章…...
从文字问诊到具身交付:健康 Agent 产品落地实战
当健康 Agent 跳出冰冷文字框,升级为能开口、有表情、会互动的 3D 数字医生,产品体验会发生质变。本文完整记录:我如何用魔珐星云 SDK,把文字健康 Agent 落地为可交互数字员工的实战全过程。 一、产品痛点:健康 Agent&…...
Agent Runtime 重构:Session 作为事件日志的工程实践
1. 这不是新赛道,而是 runtime 层的“操作系统时刻”正在重演你有没有试过让一个 AI 代理连续工作四十分钟?不是闲聊,而是真干活:查数据库、调 API、读文档、写代码、改配置、再验证——一环扣一环。去年我带团队跑一个客户的数据…...
干翻特斯拉?雷军说输给特斯拉不丢人
一周前的晚上,雷军和马斯克合照上了热搜。一周后的晚上,“雷军说输给特斯拉不丢人”又上了热搜。①5 月 21 日晚间小米有个发布会,雷军期间自问:“Model Y 是全球纯电车型的销冠,每年都有很多车型站出来要挑战 Model Y…...
中科院空天院团队Geography and Sustainability:1985年至2022年各国人均耕地面积差距的扩大:对实现可持续发展目标的威胁
耕地作为粮食的载体,是保障粮食安全的关键要素。全球人口增长不可避免地导致耕地扩张以满足对食物、纤维和能源日益增长的需求,这给耕地的承载能力带来沉重负担,并加速了土壤退化与流失,对实现联合国可持续发展目标2(S…...
Unity哥特UI资源包:SDF字体与Shader Graph工程化实践
1. 为什么哥特UI在游戏开发中长期被低估,又为何现在必须认真对待“哥特UI”这个词,很多Unity开发者第一反应是:不就是黑底、尖角、浮雕字、带玫瑰纹样的按钮吗?配个暗红渐变完事。我2019年接手一个中世纪黑暗奇幻RPG时也这么想——…...
fastapi · FastAPI framework, high performance, easy to learn, fast to code, ready for production
fastapi FastAPI framework, high performance, easy to learn, fast to code, ready for production 本文整理自 GitHub,经重新整理编辑。 FastAPI framework, high performance, easy to learn, fast to code, ready for production Documentation: https://fas…...
2026年免费照片去水印软件App排行榜|去水印App推荐和评测指南
照片被水印困扰是很多用户的常见问题。无论是保存网络上的精美图片、处理工作资料,还是制作个人素材库,去水印都是一个实用的需求。本篇文章根据2026年最新的工具体验,为你梳理免费照片去水印软件app有哪些、各类去水印App怎么选择࿰…...
SeekStorm入门指南:5分钟构建你的第一个高性能搜索引擎
SeekStorm入门指南:5分钟构建你的第一个高性能搜索引擎 【免费下载链接】SeekStorm SeekStorm: vector & lexical search - in-process library & multi-tenancy server, in Rust. 项目地址: https://gitcode.com/gh_mirrors/se/SeekStorm 想要快速构…...
2026年10款降AI率网站横评:最高AI率100%直降至0.12%
2026年全球学术界对AIGC内容的监管持续收紧,论文查重与AI痕迹检测标准迎来全面升级,高校及科研机构纷纷引入更精准的检测系统,导致学术不端行为面临更严苛的审查。在此背景下,论文降AI工具市场需求激增,用户规模在半年…...
大模型的伦理与合规:隐私保护、偏见与安全问题
在大模型技术深度渗透各行业的当下,软件测试从业者正站在保障AI技术负责任发展的关键节点。从医疗诊断辅助系统到金融风控模型,大模型的每一次输出都关乎用户权益与社会公平。作为质量把关人,测试从业者需突破传统软件测试边界,构…...
