以 ptp4l、E2E 为例的 Linuxptp 代码分析

news/2024/12/4 19:56:35/

最近在学习这部分内容,发现网上没有多少相关的代码分析的文章,结合自己的学习,和大家分享一下,希望大家批评指正!

linuxptp code

git clone git://git.code.sf.net/p/linuxptp/code linuxptp

ptp4l.c::main() 首先是处理命令行的参数,然后是 clock type,紧接着 clock_create,最终在 clock_poll中让端口处理他们的 events。根据不同的 clock type 有不同的 event,这里以 bc_event 为例。

port.c::bc_event
这部分我的描述可能不够准确,见谅。在我看来分为几个部分,第一个部分是 switch(fd_index),这部分是主动发出 msg,然后是中间的处理接收到的 msg 部分,最后是 switch(msg_type(msg)),这部分是收到 msg 后的回应。本文按照 E2E 的流程来分析,可以参考这个:1588的E2E链路延迟测量机制 这边还是建议对照着 E2E 的四步图来看,会更清楚一些。我根据本文的顺序也画了图,供参考。

在这里插入图片描述

本文目录(根据 E2E 四步)

  • 第一步和第二步
    • master 发送 Sync 和 Follow up
    • slave 接收 Sync
    • slave 接收 Follow up
  • 第三步
    • slave 发送 Delay_Req
  • 第四步
    • master 收到 Delay_Req 后立即发送 Delay_Resp
    • slave 收到 process_delay_resp
  • 总结语
  • 参考文献

第一步和第二步

master 发送 Sync 和 Follow up

master 发送 Sync 后立刻发送 Follow up
bc_event => port_tx_sync

int port_tx_sync(struct port *p, struct address *dst)
{//这里的 msg 就是 sync,fup 就是 follow upstruct ptp_message *msg, *fup;int err, event;//首先判断 timestamping 的类型switch (p->timestamping) {case TS_SOFTWARE:case TS_LEGACY_HW:case TS_HARDWARE:event = TRANS_EVENT;break;case TS_ONESTEP:event = TRANS_ONESTEP;break;case TS_P2P1STEP:event = TRANS_P2P1STEP;break;default:return -1;}if (p->inhibit_multicast_service && !dst) {return 0;}if (!port_capable(p)) {return 0;}if (port_sync_incapable(p)) {return 0;}//alloc msg 和 fupmsg = msg_allocate();if (!msg) {return -1;}fup = msg_allocate();if (!fup) {msg_put(msg);return -1;}//这一部分是 sync,设置 msg 的 hwts type 和 port 相同,然后设置 ptp packet headermsg->hwts.type = p->timestamping;msg->header.tsmt               = SYNC | p->transportSpecific;msg->header.ver                = PTP_VERSION;msg->header.messageLength      = sizeof(struct sync_msg);msg->header.domainNumber       = clock_domain_number(p->clock);msg->header.sourcePortIdentity = p->portIdentity;msg->header.sequenceId         = p->seqnum.sync++;msg->header.control            = CTL_SYNC;msg->header.logMessageInterval = p->logSyncInterval;if (p->timestamping != TS_ONESTEP && p->timestamping != TS_P2P1STEP) {msg->header.flagField[0] |= TWO_STEP;}if (dst) {msg->address = *dst;msg->header.flagField[0] |= UNICAST;msg->header.logMessageInterval = 0x7f;}//这里就开始发送了,这里是先发送,后保存 t1,t1 将在 follow up msg 里发送给 slaveerr = port_prepare_and_send(p, msg, event);if (err) {pr_err("port %hu: send sync failed", portnum(p));goto out;}//如果是 onestep 到此为止,如果是 twostep,继续发送 follow up,不多赘述if (p->timestamping == TS_ONESTEP || p->timestamping == TS_P2P1STEP) {goto out;} else if (msg_sots_missing(msg)) {pr_err("missing timestamp on transmitted sync");err = -1;goto out;}/** Send the follow up message right away.*/fup->hwts.type = p->timestamping;fup->header.tsmt               = FOLLOW_UP | p->transportSpecific;fup->header.ver                = PTP_VERSION;fup->header.messageLength      = sizeof(struct follow_up_msg);fup->header.domainNumber       = clock_domain_number(p->clock);fup->header.sourcePortIdentity = p->portIdentity;fup->header.sequenceId         = p->seqnum.sync - 1;fup->header.control            = CTL_FOLLOW_UP;fup->header.logMessageInterval = p->logSyncInterval;//下面这一句是上面没有的,这一步是将上面得到的 ts 放入 follow up 的 msg 中,这个时刻就是 t1。fup->follow_up.preciseOriginTimestamp = tmv_to_Timestamp(msg->hwts.ts);if (dst) {fup->address = *dst;fup->header.flagField[0] |= UNICAST;}if (p->follow_up_info && follow_up_info_append(fup)) {pr_err("port %hu: append fup info failed", portnum(p));err = -1;goto out;}err = port_prepare_and_send(p, fup, TRANS_GENERAL);if (err) {pr_err("port %hu: send follow up failed", portnum(p));}
out:msg_put(msg);msg_put(fup);return err;
}
int port_prepare_and_send(struct port *p, struct ptp_message *msg,enum transport_event event)
{int cnt;if (msg_pre_send(msg)) {return -1;}if (msg_unicast(msg)) {cnt = transport_sendto(p->trp, &p->fda, event, msg);} else {cnt = transport_send(p->trp, &p->fda, event, msg);}if (cnt <= 0) {return -1;}port_stats_inc_tx(p, msg);if (msg_sots_valid(msg)) {ts_add(&msg->hwts.ts, p->tx_timestamp_offset);}return 0;
}int transport_send(struct transport *t, struct fdarray *fda,enum transport_event event, struct ptp_message *msg)
{int len = ntohs(msg->header.messageLength);//这里的 send 也是有分类的;注意这里记录了发送时的hwts,如果是two step,就会用到。return t->send(t, fda, event, 0, msg, len, NULL, &msg->hwts);
}
//以 raw_send 为例
static int raw_send(struct transport *t, struct fdarray *fda,enum transport_event event, int peer, void *buf, int len,struct address *addr, struct hw_timestamp *hwts)
{struct raw *raw = container_of(t, struct raw, t);ssize_t cnt;unsigned char pkt[1600], *ptr = buf;struct eth_hdr *hdr;int fd = -1;switch (event) {case TRANS_GENERAL:fd = fda->fd[FD_GENERAL];break;case TRANS_EVENT:case TRANS_ONESTEP:case TRANS_P2P1STEP:case TRANS_DEFER_EVENT:fd = fda->fd[FD_EVENT];break;}ptr -= sizeof(*hdr);len += sizeof(*hdr);if (!addr)addr = peer ? &raw->p2p_addr : &raw->ptp_addr;hdr = (struct eth_hdr *) ptr;addr_to_mac(&hdr->dst, addr);addr_to_mac(&hdr->src, &raw->src_addr);hdr->type = htons(ETH_P_1588);//socket sendcnt = send(fd, ptr, len, 0);if (cnt < 1) {return -errno;}/** Get the time stamp right away.*/return event == TRANS_EVENT ? sk_receive(fd, pkt, len, NULL, hwts, MSG_ERRQUEUE) : cnt;
}

slave 接收 Sync

到这里为止,已经发送了 Sync 和 Follow up,Follow up 中包含了 t1,然后 slave 收到相应的消息:
bc_event => process_sync

void process_sync(struct port *p, struct ptp_message *m)
{enum syfu_event event;switch (p->state) {case PS_INITIALIZING:case PS_FAULTY:case PS_DISABLED:case PS_LISTENING:case PS_PRE_MASTER:case PS_MASTER:case PS_GRAND_MASTER:case PS_PASSIVE:return;case PS_UNCALIBRATED:case PS_SLAVE:break;}if (check_source_identity(p, m)) {return;}if (!msg_unicast(m) &&m->header.logMessageInterval != p->log_sync_interval) {p->log_sync_interval = m->header.logMessageInterval;clock_sync_interval(p->clock, p->log_sync_interval);}m->header.correction += p->asymmetry;//这里是one step的部分if (one_step(m)) {//如果是onestep,那么 sync msg 会携带它被发送时的 ts 来,所以 t1 和 t2 直接就有了port_synchronize(p, m->header.sequenceId,m->hwts.ts, m->ts.pdu,m->header.correction, 0,m->header.logMessageInterval);flush_last_sync(p);return;}//这里应该是 p->syfu == SF_EMPTY,进而 event = SYNC_MISMATCH,这两个下面的 port_syfufsm() 会用到if (p->syfu == SF_HAVE_FUP &&fup_sync_ok(p->last_syncfup, m) &&p->last_syncfup->header.sequenceId == m->header.sequenceId) {event = SYNC_MATCH;} else {event = SYNC_MISMATCH;}port_syfufsm(p, event, m);
}static void port_syfufsm(struct port *p, enum syfu_event event,struct ptp_message *m)
{struct ptp_message *syn, *fup;switch (p->syfu) {case SF_EMPTY:switch (event) {case SYNC_MISMATCH:msg_get(m);//这里保存了 sync 这个 msg,另外还设置了 p->syfu = SF_HAVE_SYNC,下面会用到p->last_syncfup = m;p->syfu = SF_HAVE_SYNC;break;case FUP_MISMATCH:msg_get(m);p->last_syncfup = m;p->syfu = SF_HAVE_FUP;break;case SYNC_MATCH:break;case FUP_MATCH:break;}break;case SF_HAVE_SYNC:switch (event) {case SYNC_MISMATCH:port_syfufsm_print_mismatch(p, event, m);msg_put(p->last_syncfup);msg_get(m);p->last_syncfup = m;break;case SYNC_MATCH:break;case FUP_MISMATCH:port_syfufsm_print_mismatch(p, event, m);msg_put(p->last_syncfup);msg_get(m);p->last_syncfup = m;p->syfu = SF_HAVE_FUP;break;case FUP_MATCH:syn = p->last_syncfup;port_synchronize(p, syn->header.sequenceId,syn->hwts.ts, m->ts.pdu,syn->header.correction,m->header.correction,m->header.logMessageInterval);msg_put(p->last_syncfup);p->syfu = SF_EMPTY;break;}break;case SF_HAVE_FUP:switch (event) {case SYNC_MISMATCH:port_syfufsm_print_mismatch(p, event, m);msg_put(p->last_syncfup);msg_get(m);p->last_syncfup = m;p->syfu = SF_HAVE_SYNC;break;case SYNC_MATCH:fup = p->last_syncfup;port_synchronize(p, fup->header.sequenceId,m->hwts.ts, fup->ts.pdu,m->header.correction,fup->header.correction,m->header.logMessageInterval);msg_put(p->last_syncfup);p->syfu = SF_EMPTY;break;case FUP_MISMATCH:port_syfufsm_print_mismatch(p, event, m);msg_put(p->last_syncfup);msg_get(m);p->last_syncfup = m;break;case FUP_MATCH:break;}break;}
}

slave 接收 Follow up

bc_event => process_follow_up

void process_follow_up(struct port *p, struct ptp_message *m)
{enum syfu_event event;switch (p->state) {case PS_INITIALIZING:case PS_FAULTY:case PS_DISABLED:case PS_LISTENING:case PS_PRE_MASTER:case PS_MASTER:case PS_GRAND_MASTER:case PS_PASSIVE:return;case PS_UNCALIBRATED:case PS_SLAVE:break;}if (check_source_identity(p, m)) {return;}if (p->follow_up_info) {struct follow_up_info_tlv *fui = follow_up_info_extract(m);if (!fui)return;clock_follow_up_info(p->clock, fui);}//接上一个,p->syfu == SF_HAVE_SYNC,进而 event = FUP_MATCHif (p->syfu == SF_HAVE_SYNC &&p->last_syncfup->header.sequenceId == m->header.sequenceId) {event = FUP_MATCH;} else {event = FUP_MISMATCH;}port_syfufsm(p, event, m);
}static void port_syfufsm(struct port *p, enum syfu_event event,struct ptp_message *m)
{struct ptp_message *syn, *fup;switch (p->syfu) {case SF_EMPTY:switch (event) {case SYNC_MISMATCH:msg_get(m);p->last_syncfup = m;p->syfu = SF_HAVE_SYNC;break;case FUP_MISMATCH:msg_get(m);p->last_syncfup = m;p->syfu = SF_HAVE_FUP;break;case SYNC_MATCH:break;case FUP_MATCH:break;}break;case SF_HAVE_SYNC:switch (event) {case SYNC_MISMATCH:port_syfufsm_print_mismatch(p, event, m);msg_put(p->last_syncfup);msg_get(m);p->last_syncfup = m;break;case SYNC_MATCH:break;case FUP_MISMATCH:port_syfufsm_print_mismatch(p, event, m);msg_put(p->last_syncfup);msg_get(m);p->last_syncfup = m;p->syfu = SF_HAVE_FUP;break;case FUP_MATCH://获取上一个 msg,在上面一个里面保存了 p->last_syncfup = m;syn = p->last_syncfup;//syn->hwts.ts 这个应该是 t2,也就是 sync msg 到达 slave 的时间。m->ts.pdu 应该是 t1,也就是 follow 携带来的 ts。port_synchronize(p, syn->header.sequenceId,syn->hwts.ts, m->ts.pdu,syn->header.correction,m->header.correction,m->header.logMessageInterval);msg_put(p->last_syncfup);//这里改了 p->syfu,这样就形成了一个循环p->syfu = SF_EMPTY;break;}break;case SF_HAVE_FUP:switch (event) {case SYNC_MISMATCH:port_syfufsm_print_mismatch(p, event, m);msg_put(p->last_syncfup);msg_get(m);p->last_syncfup = m;p->syfu = SF_HAVE_SYNC;break;case SYNC_MATCH:fup = p->last_syncfup;port_synchronize(p, fup->header.sequenceId,m->hwts.ts, fup->ts.pdu,m->header.correction,fup->header.correction,m->header.logMessageInterval);msg_put(p->last_syncfup);p->syfu = SF_EMPTY;break;case FUP_MISMATCH:port_syfufsm_print_mismatch(p, event, m);msg_put(p->last_syncfup);msg_get(m);p->last_syncfup = m;break;case FUP_MATCH:break;}break;}
}static void port_synchronize(struct port *p,uint16_t seqid,tmv_t ingress_ts,struct timestamp origin_ts,Integer64 correction1, Integer64 correction2,Integer8 sync_interval)
{enum servo_state state, last_state;tmv_t t1, t1c, t2, c1, c2;port_set_sync_rx_tmo(p);//fup 携带的 t1t1 = timestamp_to_tmv(origin_ts);//ingress 获得 t2t2 = ingress_ts;c1 = correction_to_tmv(correction1);c2 = correction_to_tmv(correction2);t1c = tmv_add(t1, tmv_add(c1, c2));switch (p->state) {case PS_UNCALIBRATED:case PS_SLAVE://这里主要是 record t1,t2monitor_sync(p->slave_event_monitor,clock_parent_identity(p->clock), seqid,t1, tmv_add(c1, c2), t2);break;default:break;}last_state = clock_servo_state(p->clock);state = clock_synchronize(p->clock, t2, t1c);switch (state) {case SERVO_UNLOCKED:port_dispatch(p, EV_SYNCHRONIZATION_FAULT, 0);if (servo_offset_threshold(clock_servo(p->clock)) != 0 &&sync_interval != p->initialLogSyncInterval) {p->logPdelayReqInterval = p->logMinPdelayReqInterval;p->logSyncInterval = p->initialLogSyncInterval;port_tx_interval_request(p, SIGNAL_NO_CHANGE,SIGNAL_SET_INITIAL,SIGNAL_NO_CHANGE);}break;case SERVO_JUMP:port_dispatch(p, EV_SYNCHRONIZATION_FAULT, 0);flush_delay_req(p);if (p->peer_delay_req) {msg_put(p->peer_delay_req);p->peer_delay_req = NULL;}break;case SERVO_LOCKED:port_dispatch(p, EV_MASTER_CLOCK_SELECTED, 0);break;case SERVO_LOCKED_STABLE:message_interval_request(p, last_state, sync_interval);break;}
}

第三步

slave 发送 Delay_Req

slave 收到 master 的 msg 后,需要回复 Delay_Req
bc_event => port_delay_request

int port_delay_request(struct port *p)
{struct ptp_message *msg;/* Time to send a new request, forget current pdelay resp and fup */if (p->peer_delay_resp) {msg_put(p->peer_delay_resp);p->peer_delay_resp = NULL;}if (p->peer_delay_fup) {msg_put(p->peer_delay_fup);p->peer_delay_fup = NULL;}if (p->delayMechanism == DM_P2P) {return port_pdelay_request(p);}msg = msg_allocate();if (!msg) {return -1;}msg->hwts.type = p->timestamping;msg->header.tsmt               = DELAY_REQ | p->transportSpecific;msg->header.ver                = PTP_VERSION;msg->header.messageLength      = sizeof(struct delay_req_msg);msg->header.domainNumber       = clock_domain_number(p->clock);msg->header.correction         = -p->asymmetry;msg->header.sourcePortIdentity = p->portIdentity;msg->header.sequenceId         = p->seqnum.delayreq++;msg->header.control            = CTL_DELAY_REQ;msg->header.logMessageInterval = 0x7f;if (p->hybrid_e2e) {struct ptp_message *dst = TAILQ_FIRST(&p->best->messages);msg->address = dst->address;msg->header.flagField[0] |= UNICAST;}//这里仍会调用 sk_receive 保存 ts,即 t3,不过这里的 ts 并没有用if (port_prepare_and_send(p, msg, TRANS_EVENT)) {pr_err("port %hu: send delay request failed", portnum(p));goto out;}if (msg_sots_missing(msg)) {pr_err("missing timestamp on transmitted delay request");goto out;}TAILQ_INSERT_HEAD(&p->delay_req, msg, list);return 0;
out:msg_put(msg);return -1;
}

第四步

master 收到 Delay_Req 后立即发送 Delay_Resp

master 收到 Delay_Req 后立即发送 Delay_Resp,这一步的目的是为了把 t4 发送给 slave。

static int process_delay_req(struct port *p, struct ptp_message *m)
{int err, nsm, saved_seqnum_sync;struct ptp_message *msg;nsm = port_nsm_reply(p, m);if (!nsm && p->state != PS_MASTER && p->state != PS_GRAND_MASTER) {return 0;}if (p->delayMechanism == DM_P2P) {pr_warning("port %hu: delay request on P2P port", portnum(p));return 0;}msg = msg_allocate();if (!msg) {return -1;}msg->hwts.type = p->timestamping;msg->header.tsmt               = DELAY_RESP | p->transportSpecific;msg->header.ver                = PTP_VERSION;msg->header.messageLength      = sizeof(struct delay_resp_msg);msg->header.domainNumber       = m->header.domainNumber;msg->header.correction         = m->header.correction;msg->header.sourcePortIdentity = p->portIdentity;msg->header.sequenceId         = m->header.sequenceId;msg->header.control            = CTL_DELAY_RESP;msg->header.logMessageInterval = p->logMinDelayReqInterval;//这一步需要将 t4 放到 Delay_Resp msg 中,收到的 msg 的 ts 就是 t4msg->delay_resp.receiveTimestamp = tmv_to_Timestamp(m->hwts.ts);msg->delay_resp.requestingPortIdentity = m->header.sourcePortIdentity;if (p->hybrid_e2e && msg_unicast(m)) {msg->address = m->address;msg->header.flagField[0] |= UNICAST;msg->header.logMessageInterval = 0x7f;}if (nsm && net_sync_resp_append(p, msg)) {pr_err("port %hu: append NSM failed", portnum(p));err = -1;goto out;}err = port_prepare_and_send(p, msg, TRANS_GENERAL);if (err) {pr_err("port %hu: send delay response failed", portnum(p));goto out;}if (nsm) {saved_seqnum_sync = p->seqnum.sync;p->seqnum.sync = m->header.sequenceId;err = port_tx_sync(p, &m->address);p->seqnum.sync = saved_seqnum_sync;}
out:msg_put(msg);return err;
}

slave 收到 process_delay_resp

master 发送 Delay_Resp 后,slave 接受该 msg 后,得到 t4,至此,slave 拥有了 t1 ~ t4。

void process_delay_resp(struct port *p, struct ptp_message *m)
{struct delay_resp_msg *rsp = &m->delay_resp;struct ptp_message *req;tmv_t c3, t3, t4, t4c;if (p->state != PS_UNCALIBRATED && p->state != PS_SLAVE) {return;}if (!pid_eq(&rsp->requestingPortIdentity, &p->portIdentity)) {return;}if (check_source_identity(p, m)) {return;}//这里是为了找到发送 Delay_Req 的那个 msgTAILQ_FOREACH(req, &p->delay_req, list) {if (rsp->hdr.sequenceId == ntohs(req->delay_req.hdr.sequenceId)) {break;}}if (!req) {return;}c3 = correction_to_tmv(m->header.correction);//得到 t3t3 = req->hwts.ts;//t4 从 Delay_Resp msg 中获得t4 = timestamp_to_tmv(m->ts.pdu);t4c = tmv_sub(t4, c3);//record t3 和 t4monitor_delay(p->slave_event_monitor, clock_parent_identity(p->clock),m->header.sequenceId, t3, c3, t4);clock_path_delay(p->clock, t3, t4c);TAILQ_REMOVE(&p->delay_req, req, list);msg_put(req);if (p->logMinDelayReqInterval == rsp->hdr.logMessageInterval) {return;}if (msg_unicast(m)) {/* Unicast responses have logMinDelayReqInterval set to 0x7F. */return;}if (rsp->hdr.logMessageInterval < -10 ||rsp->hdr.logMessageInterval > 22) {pl_info(300, "port %hu: ignore bogus delay request interval 2^%d",portnum(p), rsp->hdr.logMessageInterval);return;}p->logMinDelayReqInterval = rsp->hdr.logMessageInterval;pr_notice("port %hu: minimum delay request interval 2^%d",portnum(p), p->logMinDelayReqInterval);port_set_delay_tmo(p);
}

总结语

至此,E2E 的四步就已经分析完了,要学习 linuxptp 的背景和原理,可以 Google 更多的文章学习,在跑 ptp4l hardware mode 的过程中可能会遇到一些问题,有机会还会再开一篇文章。最近补充了一些内容,放在后续的文章里:补充:以 ptp4l、E2E 为例的 Linuxptp 代码分析

如果觉得这篇文章有用的话,可以点赞、评论或者收藏,万分感谢,goodbye~

参考文献

Implementing IEEE 1588v2 for use in the mobile backhaul


http://www.ppmy.cn/news/638301.html

相关文章

PTP4L命令手册(谷歌翻译)

ptp4l-PTP边界/普通时钟概要ptp4l [-AEP246HSLmqsv] [-f config] [-p phc-device] [-l打印级别] [-i接口] ...描述ptp4l是根据Linux的IEEE标准1588的精确时间协议&#xff08;PTP&#xff09;的实现。它实现了边界时钟&#xff08;BC&#xff09;和普通时钟&#xff08;OC&…

linuxptp/ptp4l PTP时钟同步配置选项

欢迎淘宝搜索飞灵科技&#xff0c;我司相关新产品陆续上线。 匆忙翻译了一下linuxptp ptp4l的帮助手册&#xff0c;不尽准确&#xff0c;不尽详细&#xff0c;仅供参考。欢迎留言讨论相关问题。 1 名称 ptp4l - PTP普通时钟/边界时钟/透明时钟协议栈 2 描述 ptp4l是一个Lin…

用ptp4l和phc2sys实现系统时钟同步

linuxptp用于时钟同步。 安装方法&#xff1a; ubuntu平台上用apt,也可以用官方的github源码自行编译&#xff1a; https://github.com/richardcochran/linuxptp apt install linuxptp linuxptp主要包含2个程序&#xff1a;ptp4l和phc2sys 1.ptp4l 进行时钟同步&#xff0c;实时…

linux libv4l dev下载,libv4l-dev和libv4l2-dev在哪里可以下载

大家好&#xff0c;在对vlc-1.1.7源码进行./configure操作的时候&#xff0c;出现这样的提示&#xff1a; configure: WARNING: LibV4L support disabled because libv4l development headers were not found checking linux/videodev2.h usability... yes checking linux/vide…

ptp4l linux,如何使用PTP4l测试PTPV2协议精度?

1、首先安装ptp4l的方式 http://linuxptp.sourceforge.net/ 下载和安装方式请参照此网址&#xff0c;下载开源代码&#xff0c;然后使用make &#xff0c;make install 手动安装 或者使用yum install ptp4l 进行安装 2、ptp4l环境搭建 使用ptp4l测试环境&#xff0c;需要两个机…

【echarts】vue+echarts树状图示例,文字显示不全是因为少了中括号[]

效果图献上&#xff1a; 完整代码如下: <template><div><div id"treeChart" :style"{width: 500px, height: 500px, padding: 30px}"></div></div> </template><script> export default {name: "eCharts&…

【C语言】最佳找零组合

这是一个生活实际问题&#xff0c;当你购买了不到100元的商品&#xff0c;却交给了售货员一张100元的货币…售货员就得考虑如何组合给你找钱。 题目:找零钱最佳组合 假设商店货品价格皆不大于100元(且为整数),若顾客付款在100元,求找给顾客之最少货币张数? (货币面值50元&…

如何搭建物联网阿里云平台

如何搭建物联网阿里云平台**背景介绍&#xff1a;**基于NB-M50平台下的阿里云平台的搭建 **目的&#xff1a;**实现NB-M50开发板和阿里云平台的相互通信 1.网页搭建及添加设备 1.1首先需要访问阿里云官网&#xff0c;点击右上角的免费注册或者点击登录&#xff0c;用淘宝、支付…