当前位置: 首页 > news >正文

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.csd_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.说明 以前看内核代码觉得难&#xff0c;是因为内核代码涉及到硬件原理与算法结构和层次递进的代码逻辑&#xff0c;现在的应用层因为业务的复杂与代码和内核的交互接口复杂&#xff0c;也变得有些难度了。 这篇文章是继:openbmc dbus架构简析的第二篇文章。 首先贴出来前篇…...

【二分查找】Leetcode例题

【1】69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09; &#x1f361;解题思路&#xff1a;首先想到的是暴力查找&#xff0c;从1开始依次比较x与num*num的大小&#xff0c;然后找出满足num*num<x且(num1)*(num1)>x的num值&#xff1b;再来看看能不能优化一下&…...

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等)并修改名称下载

大家好&#xff0c;今天给大家分享一篇关于在Vue项目中展示附件&#xff08;PDF、MP4、Excel、Zip等&#xff09;并修改名称下载的教程。在实际开发过程中&#xff0c;这个功能非常实用&#xff0c;下面我们就一起来学习一下。 一、准备工作 首先&#xff0c;确保你的项目中已经…...

AI证件照制作 API 对接说明

AI证件照制作 API 对接说明 本文将介绍一种 AI证件照制作 API 对接说明&#xff0c;它是可以通过输入人像照片URL以及自己喜欢的模板来制作各种风格的证件照。 接下来介绍下 AI证件照制作 API 的对接说明。 申请流程 要使用 API&#xff0c;需要先到 AI证件照制作 API?inv…...

Macos用brew安装Nodejs亲手教程

首先确保brew已安装&#xff0c;搜索node资源&#xff0c;命令如下&#xff1a; brew search nodejs 演示结果如下&#xff1a; 安装nodejs brew install node22 或 brew install node 出现如下界面 表示正在安装&#xff0c;安装成功后&#xff0c;提示如下信息&#xff1…...

Node.js 新手教程

1、nodejs简介 Node.js 是一个开源和跨平台的 JavaScript 运行时环境。它是几乎所有类型项目的流行工具&#xff01; Node.js 在浏览器之外运行 V8 JavaScript 引擎&#xff08;Google Chrome 的核心&#xff09;。这使得 Node.js 的性能非常出色。 Node.js 应用程序在单个进…...

Latex转word(docx)或者说PDF转word 一个相对靠谱的方式

0. 前言 投文章过程中总会有各种各样的要求&#xff0c;其中提供word格式的手稿往往是令我头疼的一件事。尤其在多公式的文章中&#xff0c;其中公式转换是一个头疼的地方&#xff0c;还有很多图表&#xff0c;格式等等&#xff0c;想想就让人头疼欲裂。实践中摸索出一条相对靠…...

前端热门面试题目——React、Node

img 标签的 srcset 属性的作用 srcset 属性允许开发者为不同设备或分辨率提供多个图像选项&#xff0c;优化加载的图片以适应设备的屏幕大小和分辨率。这提高了性能和用户体验。 示例&#xff1a; <img src"default.jpg" srcset"small.jpg 480w, medium.j…...

Ansible自动化一键部署单节点集群架构

自动化部署利器&#xff1a;Ansible 一键部署脚本 在现代IT基础设施管理中&#xff0c;Ansible以其简洁、强大的自动化能力脱颖而出。以下是精心打造的Ansible自动化一键部署脚本&#xff0c;旨在简化部署流程&#xff0c;提升效率&#xff0c;确保一致性和可靠性。 通过这个…...

电脑插入耳机和音响,只显示一个播放设备

1. 控制面板-硬件和声音-Realtek高清音频-扬声器-设备高级设置-播放设备里选择使用前部和后部输出设备同时播放两种不同的音频流 在声音设置中就可以看到耳机播放选项...

家政小程序开发,打造便捷家政生活小程序

目前&#xff0c;随着社会人就老龄化和生活压力的加重&#xff0c;家政服务市场的需求正在不断上升&#xff0c;家政市场的规模也正在逐渐扩大&#xff0c;发展前景可观。 在市场快速发展的影响下&#xff0c;越来越多的企业开始进入到市场中&#xff0c;同时家政市场布局也发…...

tcpdump抓包wireshark分析

背景 分析特定协议的数据包&#xff0c;如 HTTP、DNS、TCP、UDP 等&#xff0c;诊断网络问题&#xff0c;例如连接故障、延迟和数据包丢失。 大概过程 1.安装tcpdump yum update yum install tcpdump2.抓包&#xff0c;从当前时间起&#xff0c;一小时后停止&#xff0c…...

文件无法直接拖入zotero

解决方法&#xff1a;取消管理员权限打开zotero。 具体如下&#xff1a;右键zotero应用程序&#xff0c;打开属性&#xff0c;选择“兼容性”&#xff0c;点击底下的“更改所有用户的设置”&#xff0c;在弹出的框中取消“以管理员身份运行此程序”。如下所示&#xff1a;...

使用 useMemo 和 React.memo 优化 React 组件渲染

在 React 中&#xff0c;性能优化是一个重要的主题&#xff0c;特别是在复杂的组件树中。本文将演示如何在同一个父组件中使用 useMemo 和 React.memo 来优化子组件的渲染。 1. 组件结构 创建一个父组件&#xff0c;包含两个子组件&#xff1a; MemoChild&#xff1a;使用 R…...

ISAAC SIM踩坑记录--添加第三方3D场景

ISAAC SIM仿真首先就是要有合适的3D场景&#xff0c;官方提供了一些场景&#xff0c;如果不能满足要求&#xff0c;那就只能自己建。 对于我这种不会3D建模的菜鸟&#xff0c;只能到网上下载了&#xff0c;sketchfab就是一个不错的平台&#xff0c;有不少免费资源可以下载。 …...

Git 详解

Git 详解 Git 是一个分布式版本控制系统&#xff0c;用于高效地管理项目代码的版本历史。它是目前最流行的版本控制工具之一&#xff0c;广泛应用于软件开发领域。Git 的分布式架构允许开发者在本地进行代码的版本管理&#xff0c;并与远程仓库同步&#xff0c;实现团队协作。…...

Linux操作系统3-文件与IO操作1(从C语言IO操作到系统调用)

上篇文章&#xff1a;Linux操作系统2-进程控制3(进程替换&#xff0c;exec相关函数和系统调用)_execv系统调用-CSDN博客 本篇代码Gitee仓库&#xff1a;myLerningCode 橘子真甜/linux学习 - 码云 - 开源中国 (gitee.com) 本篇重点&#xff1a;C语言基础IO与系统调用 目录 一.…...

【Python网络爬虫笔记】8- (BeautifulSoup)抓取电影天堂2024年最新电影,并保存所有电影名称和链接

目录 一. BeautifulSoup的作用二. 核心方法介绍2.1 构造函数2.2 find()方法2.3 find_all()方法2.4 select()方法 三. 网络爬虫中使用BeautifulSoup四、案例爬取结果 一. BeautifulSoup的作用 解析HTML/XML文档&#xff1a;它可以将复杂的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 注&#xff1a;如果容器启动失败&#xff0c;参考我另外一篇文章…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

Redis:现代应用开发的高效内存数据存储利器

一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发&#xff0c;其初衷是为了满足他自己的一个项目需求&#xff0c;即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源&#xff0c;Redis凭借其简单易用、…...