技术标签: 协议栈 领居协议 邻居协议 邻居状态 linux内核协议栈
目录
4.1 首包数据发送 ip_finish_output2()
5.3 定时器超时处理 neigh_timer_handler()
邻居项的状态机机制是通用邻居层最重要的内容,主要是处理邻居项中状态的改变,其中包括几个邻居状态的定时器机制,以及邻居项的更新,solicit请求的发送等。对于通用邻居项的状态机,主要有如下几个状态:
其中,处于如下状态的邻居项,都会启动一个定时器:
#define NUD_IN_TIMER (NUD_INCOMPLETE|NUD_REACHABLE|NUD_DELAY|NUD_PROBE)
而处于如下状态的邻居项,我们认为邻居项是可达的:
#define NUD_CONNECTED (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)
对于处于 NUD_PERMANENT、NUD_NOARP 状态的邻居项,是不会进行邻居项的状态的转换的,
从上面2个宏定义我们知道,处于NUD_REACHABLE状态的邻居项, 肯定会进行状态的状态的转变。因为处于该状态的邻居项,要启动一个定时器,那如果定时器超时后,邻居项一直没有被使用,则邻居项的状态就会转变。
对于上面3种创建邻居项,其初始状态以及状态的变化会有所不同。
疑问:因为创建的邻居项的状态为NUD_NONE,而NUD_NONE也不处于定时器状态,那么处于NUD_NONE状态的邻居项,是如何将邻居项的状态转变为NUD_INCOMPLETE的呢?对于处于NUD_STALE状态的邻居项,又是如何实现邻居项状态的转变的呢?
在上面的第 1 点中,我只是说邻居项从NUD_NONE转变为NUD_INCOMPLETE,却没有说明这个转换是如何进行的。其转变过程大致如下(此处以ipv4为例):当有数据包要发送时,首先是查找路由表,确定目的地址可达。在这个查找的过程中,若还没有与该目的地址对应的邻居项,则会创建一个邻居项,并与查找到的路由缓存相关联,此时邻居项的状态还是NUD_NONE。对于ipv4来说,接着就会执行ip_output,然后就会调用到ip_finish_output2,接着就会调用到neighbour->output,而在neighbour->output里就会调用到__neigh_event_send判断数据包是否可以直接发送出去,如果此时邻居项的状态为NUD_NONE,则会将邻居项的状态设置为NUD_INCOMPLETE,并将要发送的数据包缓存到邻居项的队列中。而处于NUD_INCOMPLETE状态的邻居项的状态转变会有定时器处理函数来实现。对于处于NUD_STALE状态的邻居项,有两个条件实现状态的转变:
1、对于NUD_INCOMPLETE,当本机发送完arp 请求包后,还未收到应答时,即会进入该状态。 进入该状态,即会启动定时器,如果在定时器到期后,还没有收到应答时:如果没有到达最大发包上限时,即会重新进行发送请求报文;如果超过最大发包上限还没有收到应答,则会将状态设置为NUD_FAILED
2、对于收到可到达性确认后,即会进入NUD_REACHABLE,当进入NUD_REACHABLE状态。当进入NUD_REACHABLE后,即会启动一个定时器,当定时器到时前,该邻居协议没有被使用过,就会将邻居项的状态转换为NUD_STALE
3、对于进入NUD_STALE状态的邻居项,即会启动一个定时器。如果在定时器到时前,有数据需要发送,则直接将数据包发送出去,并将状态设置为NUD_DELAY;如果在定时器到时,没有数据需要发送,且该邻居项的引用计数为1,则会通过垃圾回收机制,释放该邻居项对应的缓存
4、处于NUD_DELAY状态的邻居项,如果在定时器到时后,没有收到可到达性确认,则会进入NUD_PROBE状态;如果在定时器到达之前,收到可到达性确认,则会进入NUD_REACHABLE (在该状态下的邻居项不会发送solicit请求,而只是等待可到达性应答。主要包括对以前的solicit请求的应答或者收到一个对于本设备以前发送的一个数据包的应答)
5、处于NUD_PROBE状态的邻居项,会发送arp solicit请求(ipv4、ipv6则是NS请求),并启动一个定时器。如果在定时器到时前,收到可到达性确认,则进入NUD_REACHABLE;如果在定时器到时后,没有收到可到达性确认:
a)没有超过最大发包次数时,则继续发送solicit请求,并启动定时器
b)如果超过最大发包次数,则将邻居项状态设置为NUD_FAILED
下图是邻居项的状态转换逻辑图,通过上面的描述和下面的逻辑图,能够很好的理解邻居项的状态机机制。
当第一包数据要发送出去,我们还不知道目的三层地址对应的二层地址或者下一跳网关对应的二层地址,对邻居项的创建(我们以ipv4为例)。在ip_finish_output2() 接口中先是获取网关(下一跳)的三层地址,根据取得三层地址(即领居地址)获取领居表项,获取不到则用网关地址创建一个。核心就是通过下一跳网关地址 nexthop 和 net_dev 为关键字查找一个neighbour项:
若查找到,则直接调用 dst_neigh_output() 发送数据。
若没有查找到,则调用neigh_create() 创建一个邻居表项并加入到arp_table的邻居表项链表中。
static inline int ip_finish_output2(struct sk_buff *skb)
{
struct dst_entry *dst = skb_dst(skb);
struct rtable *rt = (struct rtable *)dst;
struct net_device *dev = dst->dev;
unsigned int hh_len = LL_RESERVED_SPACE(dev);
struct neighbour *neigh;
u32 nexthop;
...
rcu_read_lock_bh();
nexthop = (__force u32) rt_nexthop(rt, ip_hdr(skb)->daddr);
neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
if (unlikely(!neigh))
neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);
if (!IS_ERR(neigh)) {
int res = dst_neigh_output(dst, neigh, skb);
rcu_read_unlock_bh();
return res;
}
rcu_read_unlock_bh();
net_dbg_ratelimited("%s: No header cache and no neighbour!\n",
__func__);
kfree_skb(skb);
return -EINVAL;
}
static inline void dst_confirm(struct dst_entry *dst)
{
dst->pending_confirm = 1;
}
static inline int dst_neigh_output(struct dst_entry *dst, struct neighbour *n,
struct sk_buff *skb)
{
const struct hh_cache *hh;
...
hh = &n->hh;
if ((n->nud_state & NUD_CONNECTED) && hh->hh_len)
return neigh_hh_output(hh, skb);
else
return n->output(n, skb);//调用 neigh_resolve_output()
}
执行完上面邻居项的创建以后,后面neigh_resolve_output会间接调用函数neigh_event_send(),实现邻居项状态从 NUD_NONE 到 NUD_INCOMPLETTE 状态的改变。
在arp的接收处理函数arp_rcv里,在对arp包的头部信息进行检查以及防火墙规则检查以后,对于允许接收的arp包,则会调用arp_process进行后续的处理,而在arp_process中,对于接收到的arp请求报文后,则会调用 neigh_event_ns() 进行邻居项的查找与创建功能,对于新创建的邻居项,则会调用函数 neigh_update() 更新邻居项的状态。
函数neigh_event_ns() 的逻辑流程还是比较简单的,主要是将邻居项的状态设置为 NUD_STALE
struct neighbour *neigh_event_ns(struct neigh_table *tbl,
u8 *lladdr, void *saddr,
struct net_device *dev)
{
//找不到就创建一个neigh,状态设置成 NUD_NONE
struct neighbour *neigh = __neigh_lookup(tbl, saddr, dev,
lladdr || !dev->addr_len);
if (neigh)
neigh_update(neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_OVERRIDE);
return neigh;
}
对于第三种创建邻居项的执行流程,是通过netlink消息机制实现的,最终会调用函数neigh_add实现邻居项的创建。对于新创建的邻居项,则会调用函数neigh_update或者neigh_event_send实现邻居项状态的更新。
通过对于第 4 节中三种情况下邻居项的创建流程,我们发现会调用函数neigh_update、neigh_event_send进行邻居项状态的更新,其实对于处于定时器状态的邻居项,会通过定时器超时处理函数实现邻居项状态的转变,下面我们分析一下这3个函数的处理流程。
该函数的功能:邻居项的更新,主要是更新二层地址与邻居项的状态,并会根据邻居项的状态,选择相对应的输出函数。
该函数被调用的情形有:
/* Generic update routine.
-- lladdr is new lladdr or NULL, if it is not supplied.
-- new is new state.
-- flags
NEIGH_UPDATE_F_OVERRIDE allows to override existing lladdr,
if it is different.
NEIGH_UPDATE_F_WEAK_OVERRIDE will suspect existing "connected"
lladdr instead of overriding it
if it is different.
It also allows to retain current state
if lladdr is unchanged.
NEIGH_UPDATE_F_ADMIN means that the change is administrative.
NEIGH_UPDATE_F_OVERRIDE_ISROUTER allows to override existing
NTF_ROUTER flag.
NEIGH_UPDATE_F_ISROUTER indicates if the neighbour is known as
a router.
Caller MUST hold reference count on the entry.
*/
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
u32 flags)
{
u8 old;
int err;
int notify = 0;
struct net_device *dev;
int update_isrouter = 0;
write_lock_bh(&neigh->lock);
dev = neigh->dev;
old = neigh->nud_state;
err = -EPERM;
//对于邻居项的原状态为NOARP或者PERMANENT,且不是admin发送的请求,则直接返回
if (!(flags & NEIGH_UPDATE_F_ADMIN) &&
(old & (NUD_NOARP | NUD_PERMANENT)))
goto out;
/*
当邻居项状态不是有效状态时(即是NUD_INCOMPLET、NUD_NONE、NUD_FAILED):
1、删除该邻居项的定时器。
2、对于原状态是 CONNECT 而新状态不是有效态时,则调用 neigh_suspect() 将邻居项的
输出函数设置为通用输出函数。
3、如果原状态是 NUD_INCOMPLETE 或者 NUD_PROBE,且新状态为 NUD_FAILED 时,则调用
neigh_invalidate() 发送错误报告,并发送通知信息,函数返回。
*/
if (!(new & NUD_VALID)) {
neigh_del_timer(neigh);
if (old & NUD_CONNECTED)
neigh_suspect(neigh);
neigh->nud_state = new;
err = 0;
notify = old & NUD_VALID;
if ((old & (NUD_INCOMPLETE | NUD_PROBE)) &&
(new & NUD_FAILED)) {
neigh_invalidate(neigh);
notify = 1;
}
goto out;
}
/*
1、对于设备二层地址长度为0的情形,则不需要更新二层地址,直接使用neigh->ha
2、原状态为有效的,且要更改的地址与邻居项存储的地址相同,则无需更改
3、原状态为无效,且要更改的地址也是无效,则是逻辑错误,函数直接返回
4、原状态有效,且要更改的地址无效时,则先将地址设置为邻居项的地址. ???
5、其他情况下不更改传进来的二层地址。
即:原状态有效,且修改的地址与原邻居项地址不同
原状态无效,且修改的地址有效时
*/
/* Compare new lladdr with cached one */
if (!dev->addr_len) {
/* First case: device needs no address. */
lladdr = neigh->ha;
} else if (lladdr) {
/* The second case: if something is already cached
and a new address is proposed:
- compare new & old
- if they are different, check override flag
*/
if ((old & NUD_VALID) &&
!memcmp(lladdr, neigh->ha, dev->addr_len))
lladdr = neigh->ha;
} else {
/* No address is supplied; if we know something,
use it, otherwise discard the request.
*/
err = -EINVAL;
if (!(old & NUD_VALID))
goto out;
lladdr = neigh->ha;
}
//邻居项的新状态是CONNECT时,更新connect时间
if (new & NUD_CONNECTED)
neigh->confirmed = jiffies;
//更新update时间
neigh->updated = jiffies;
/* If entry was valid and address is not changed,
do not change entry state, if new one is STALE.
*/
err = 0;
update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;
/*
1、原状态有效,且不允许覆盖原来值时,且二层地址不同,且原状态为
CONNECT时,则不更新邻居项的二层地址,而只是将状态设置为STALE
(这是在二层地址不同时,不修改二层地址的最后一个条件)
2、原状态有效,且二层地址不变,且新状态为STALE时,则不改邻居项的状态
*/
if (old & NUD_VALID) {
if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {
update_isrouter = 0;
if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) &&
(old & NUD_CONNECTED)) {
lladdr = neigh->ha;
new = NUD_STALE;
} else
goto out;
} else {
if (lladdr == neigh->ha && new == NUD_STALE &&
((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) ||
(old & NUD_CONNECTED))
)
new = old;
}
}
/*
当邻居项的新旧状态不同时,则会删除定时器。
若新状态是 NUD_TIMER,则重新添加定时器。(其中对于定时器的超时时间,如果新状态为NUD_REACHABLE,则将超时时间设置为reachable_time,否则将定时器的超时时间设置为当前时间)
设置邻居项的状态为新状态
*/
if (new != old) {
neigh_del_timer(neigh);
if (new & NUD_IN_TIMER)
neigh_add_timer(neigh, (jiffies +
((new & NUD_REACHABLE) ?
neigh->parms->reachable_time :
0)));
neigh->nud_state = new;
}
/*
如果邻居项的二层地址不同,则更新邻居项里的二层地址,并调用neigh_update_hhs,
更新与该邻居项相关联的所有二层头部缓存。如果新状态不是CONNECT状态,
则将confirm时间设置为比当前时间早2*base_reachable_time.
根据邻居项的不同更新邻居项的输出函数:
当为NUD_CONNECTED,则调用neigh_connect将邻居项的输出函数设置为快速输出函数
当为非NUD_CONNECTED,则调用neigh_suspect将邻居项的输出函数设置为通用输出函数
*/
if (lladdr != neigh->ha) {
write_seqlock(&neigh->ha_lock);
memcpy(&neigh->ha, lladdr, dev->addr_len);
write_sequnlock(&neigh->ha_lock);
neigh_update_hhs(neigh);
if (!(new & NUD_CONNECTED))
neigh->confirmed = jiffies -
(neigh->parms->base_reachable_time << 1);
notify = 1;
}
if (new == old)
goto out;
if (new & NUD_CONNECTED)
neigh_connect(neigh);
else
neigh_suspect(neigh);
if (!(old & NUD_VALID)) {
struct sk_buff *skb;
/* Again: avoid dead loop if something went wrong */
while (neigh->nud_state & NUD_VALID &&
(skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
struct dst_entry *dst = skb_dst(skb);
struct neighbour *n2, *n1 = neigh;
write_unlock_bh(&neigh->lock);
rcu_read_lock();
/* Why not just use 'neigh' as-is? The problem is that
* things such as shaper, eql, and sch_teql can end up
* using alternative, different, neigh objects to output
* the packet in the output path. So what we need to do
* here is re-lookup the top-level neigh in the path so
* we can reinject the packet there.
*/
n2 = NULL;
if (dst) {
n2 = dst_neigh_lookup_skb(dst, skb);
if (n2)
n1 = n2;
}
n1->output(n1, skb);
if (n2)
neigh_release(n2);
rcu_read_unlock();
write_lock_bh(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);
neigh->arp_queue_len_bytes = 0;
}
out:
if (update_isrouter) {
neigh->flags = (flags & NEIGH_UPDATE_F_ISROUTER) ?
(neigh->flags | NTF_ROUTER) :
(neigh->flags & ~NTF_ROUTER);
}
write_unlock_bh(&neigh->lock);
if (notify)
neigh_update_notify(neigh);
return err;
}
1、对于connect、delay、probe状态的邻居项,返回0
2、当状态为NUD_NONE时:
3、当状态为STALE时:
4、对于处于INCOMPLETE状态的邻居项
static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
unsigned long now = jiffies;
if (neigh->used != now)
neigh->used = now;
if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
return __neigh_event_send(neigh, skb);
return 0;
}
int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
int rc;
bool immediate_probe = false;
write_lock_bh(&neigh->lock);
rc = 0;
if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
goto out_unlock_bh;
if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
unsigned long next, now = jiffies;
atomic_set(&neigh->probes, neigh->parms->ucast_probes);
neigh->nud_state = NUD_INCOMPLETE;
neigh->updated = now;
next = now + max(neigh->parms->retrans_time, HZ/2);
neigh_add_timer(neigh, next);
immediate_probe = true;
} else {
neigh->nud_state = NUD_FAILED;
neigh->updated = jiffies;
write_unlock_bh(&neigh->lock);
kfree_skb(skb);
return 1;
}
} else if (neigh->nud_state & NUD_STALE) {
neigh_dbg(2, "neigh %p is delayed\n", neigh);
neigh->nud_state = NUD_DELAY;
neigh->updated = jiffies;
neigh_add_timer(neigh,
jiffies + neigh->parms->delay_probe_time);
}
if (neigh->nud_state == NUD_INCOMPLETE) {
if (skb) {
while (neigh->arp_queue_len_bytes + skb->truesize >
neigh->parms->queue_len_bytes) {
struct sk_buff *buff;
buff = __skb_dequeue(&neigh->arp_queue);
if (!buff)
break;
neigh->arp_queue_len_bytes -= buff->truesize;
kfree_skb(buff);
NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);
}
skb_dst_force(skb);
__skb_queue_tail(&neigh->arp_queue, skb);
neigh->arp_queue_len_bytes += skb->truesize;
}
rc = 1;
}
out_unlock_bh:
if (immediate_probe)
neigh_probe(neigh);
else
write_unlock(&neigh->lock);
local_bh_enable();
return rc;
}
EXPORT_SYMBOL(__neigh_event_send);
通过上面的分析,我们知道对于这几个邻居项状态的转变,最主要的就是需要定时器的那几个邻居项的状态,所以我们下面分析邻居项的定时器的处理函数,对于这几个邻居项的状态,内核使用的是同一个定时器,只不过设置的超时时间不同罢了。
该函数的功能:邻居项最主要的定时器超时处理函数,实现了诸多邻居项状态的转换以及邻居项 solicit 请求相关的函数
1、如果邻居项的当前状态不属于NUD_IN_TIMER,则函数返回。
2、对于处于reach状态的邻居项:
3、对于Delay状态的邻居项:
4、对于probe与incomplete状态的邻居项:
5、对于probe与incomplete状态的邻居项:
/* Called when a timer expires for a neighbour entry. */
static void neigh_timer_handler(unsigned long arg)
{
unsigned long now, next;
struct neighbour *neigh = (struct neighbour *)arg;
unsigned int state;
int notify = 0;
write_lock(&neigh->lock);
state = neigh->nud_state;
now = jiffies;
next = now + HZ;
if (!(state & NUD_IN_TIMER))
goto out;
if (state & NUD_REACHABLE) {
if (time_before_eq(now,
neigh->confirmed + neigh->parms->reachable_time)) {
neigh_dbg(2, "neigh %p is still alive\n", neigh);
next = neigh->confirmed + neigh->parms->reachable_time;
} else if (time_before_eq(now,
neigh->used + neigh->parms->delay_probe_time)) {
neigh_dbg(2, "neigh %p is delayed\n", neigh);
neigh->nud_state = NUD_DELAY;
neigh->updated = jiffies;
neigh_suspect(neigh);
next = now + neigh->parms->delay_probe_time;
} else {
neigh_dbg(2, "neigh %p is suspected\n", neigh);
neigh->nud_state = NUD_STALE;
neigh->updated = jiffies;
neigh_suspect(neigh);
notify = 1;
}
} else if (state & NUD_DELAY) {
if (time_before_eq(now,
neigh->confirmed + neigh->parms->delay_probe_time)) {
neigh_dbg(2, "neigh %p is now reachable\n", neigh);
neigh->nud_state = NUD_REACHABLE;
neigh->updated = jiffies;
neigh_connect(neigh);
notify = 1;
next = neigh->confirmed + neigh->parms->reachable_time;
} else {
neigh_dbg(2, "neigh %p is probed\n", neigh);
neigh->nud_state = NUD_PROBE;
neigh->updated = jiffies;
atomic_set(&neigh->probes, 0);
next = now + neigh->parms->retrans_time;
}
} else {
/* NUD_PROBE|NUD_INCOMPLETE */
next = now + neigh->parms->retrans_time;
}
if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) &&
atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) {
neigh->nud_state = NUD_FAILED;
notify = 1;
neigh_invalidate(neigh);
}
if (neigh->nud_state & NUD_IN_TIMER) {
if (time_before(next, jiffies + HZ/2))
next = jiffies + HZ/2;
if (!mod_timer(&neigh->timer, next))
neigh_hold(neigh);
}
if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) {
neigh_probe(neigh);
} else {
out:
write_unlock(&neigh->lock);
}
if (notify)
neigh_update_notify(neigh);
neigh_release(neigh);
}
在申请邻居项的内存函数neigh_alloc()里,会创建该定时器,并会将定时器的超时处理函数设置为 neigh_timer_handler。
至此,大致分析完了通用邻居项的工作流程,通过这次认真的阅读通用邻居层的代码机制,对于以后编程应该会有影响。通过分析该子层,对于一个比较好的子层代码:
文章浏览阅读44次。从CIO、CEO、CFO、COO...到CVO 这22个你了解几个? (史上最完整版)1.CEO:是Chief Executive Officer的缩写,即首席执行官. 由于市场风云变幻,决策的速度和执行的力度比以往任何时候都更加重要.传统的“董事会决策.经理层执行”的公司体制..._安卓进程保活
文章浏览阅读2.2w次,点赞582次,收藏582次。数据准备:从公共数据集中获取数据,并进行数据清洗和特征工程处理,将数据转化为适合机器学习算法处理的格式。创建SageMaker Notebook实例:通过AWS Management Console或AWS SDK创建SageMaker Notebook实例,并连接到实例。编写代码:在Notebook中编写代码,使用Amazon SageMaker提供的XGBoost算法和数据输入通道,加载并处理数据,训练并评估模型。模型调优:通过调整模型的参数和超参数,优化模型性能。_机器学习模型的云端服务器部署
文章浏览阅读4.9k次。Sublime text 3搭建Python开发环境及常用插件安装_sublime python 环境搭建
文章浏览阅读643次。MySQL在首次安装后会执行一个安全脚本,用于设置root用户的密码以及其他安全选项。_centos7安装mysql8.0gpg密钥
文章浏览阅读864次。这种场景下,可以使用两个系列,一个系列是完整的图形,当做『背景』来表达总数值,另一个系列是使用 `symbolClip` 进行剪裁过的图形,表达当前数值。_echarts symbolboundingdata
文章浏览阅读1k次,点赞18次,收藏16次。这篇文章主要介绍了学python对电脑配置要求高吗,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获,下面让小编带着大家一起了解一下。_python机器学习需要怎样配置的电脑
文章浏览阅读508次,点赞4次,收藏8次。3.1 量化精度分析流程计算不同情况下,同一层网络输入值的余弦距离,来近似的查看每一层精度损失的情况。具体量化精度分析的流程如下:3.2 量化精度分析accuracy_analysis接口量化精度分析调用accuracy_analysis接口,推理并产生快照,也就是dump出每一层的tensor数据。会dump出包括fp32和quant两种数据类型的快照,用于计算量化误差。该接口使用的量化方式与config_中指定的一致。_if set do_quantization = true
文章浏览阅读512次,点赞15次,收藏6次。摘要:RE辐射、CE传导、BCI大电流注入,ESD静电、ISO7637瞬态脉冲干扰等EMC测试、EMC整改,一站式服务,双C报告
文章浏览阅读276次。docker安装PHP5.6_docker php5.6
文章浏览阅读1.3k次。pgbench 简介:pgbench是一种在PostgreSQL上运行基准测试的简单程序。它可能在并发的数据库会话中一遍一遍地运行相同序列的 SQL 命令,并且计算平均事务率(每秒的事务数)。默认情况下,pgbench会测试一种基于 TPC-B 但是要更宽松的场景,其中在每个事务中涉及五个SELECT、UPDATE以及INSERT命令。但是,通过编写自己的事务脚本文件很容易用来测试其他情况..._tpcb测试
文章浏览阅读1k次。ATF点滴1、设置运行时栈SP2、寄存器的保存和恢复的实现3、寄存器的保存和恢复的使用场景1、设置运行时栈SPbl31_entrypoint—>el3_entrypoint_common---->plat_set_my_stack—>platform_set_stack—>platform_get_stack动态找到该cpufunc platform_set_stackmov x9, x30 // lrbl platform_get_stackmov sp, x0r_atf-tee
文章浏览阅读134次。300多个各种类型的PPT模板下载,为您提供各种类型PPT模板、PPT图片、PPT素材、海报模板、新媒体配图等内容下载。