主要内容:客户端接收SYNACK、发送ACK,完成连接的建立。

内核版本:3.15.2

我的博客:http://blog.csdn.net/zhangskd

接收入口

tcp_v4_rcv

|--> tcp_v4_do_rcv

|-> tcp_rcv_state_process

|-> tcp_rcv_synsent_state_process

1. 状态为ESTABLISHED时,用tcp_rcv_established()接收处理。

2. 状态为LISTEN时,说明这个sock处于监听状态,用于被动打开的接收处理,包括SYN和ACK。

3. 当状态不为ESTABLISHED或TIME_WAIT时,用tcp_rcv_state_process()处理。

客户端主动建立连接时,发送SYN段后,连接的状态变为SYN_SENT。

此时如果收到SYNACK段,处理函数为tcp_rcv_state_process()。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk; #ifdef CONFIG_TCP_MD5SIG
/* We really want to reject the packet as early as possible if :
* We're expecting an MD5'd packet and this is no MD5 tcp option.
* There is an MD5 option and we're not expecting one.
*/
if (tcp_v4_inbound_md5_hash(sk, skb))
goto discard;
#endif /* 当状态为ESTABLISHED时,用tcp_rcv_established()接收处理 */
if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
struct dst_entry *dst = sk->sk_rx_dst;
sock_rps_save_rxhash(sk, skb); if (dst) {
if (inet_sk(sk)->rx_dst_ifindex != skb->skb_iif || dst->ops->check(dst, 0) == NULL) {
dst_release(dst);
sk->sk_rx_dst = NULL;
}
} /* 连接已建立时的处理路径 */
tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len);
return 0;
} /* 检查报文长度、报文校验和 */
if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
goto csum_err; /* 如果这个sock处于监听状态,被动打开时的处理,包括收到SYN或ACK */
if (sk->sk_state == TCP_LISTEN) {
/* 返回值:
* NULL,错误
* nsk == sk,接收到SYN
* nsk != sk,接收到ACK
*/
struct sock *nsk = tcp_v4_hnd_req(sk, skb); if (! nsk)
goto discard; if (nsk != sk) { /* 接收到ACK时 */
sock_rps_save_rxhash(nsk, skb); if (tcp_child_process(sk, nsk, skb)) { /* 处理新的sock */
rsk = nsk;
goto reset;
}
return 0;
}
} else
sock_rps_save_rx(sk, skb); /* 处理除了ESTABLISHED和TIME_WAIT之外的所有状态,包括SYN_SENT状态 */
if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
return 0; reset:
tcp_v4_send_reset(rsk, skb); /* 发送被动的RST包 */ discard:
kfree_skb(skb);
return 0; csum_err:
TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_CSUMERRORS);
TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_INERRS);
goto discard;
}

连接状态不为ESTABLISHED或TIME_WAIT时的处理函数为tcp_rcv_state_process()。

/* This function implements the receiving procedure of RFC 793 for
* all states except ESTABLISHED and TIME_WAIT.
*/ int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
struct request_sock *req;
int queued = 0;
bool acceptable;
u32 synack_stamp; tp->rx_opt.saw_tstamp = 0; switch (sk->sk_state) {
... case TCP_SYN_SENT: /* 处理SYN_SENT状态,主要做了:
* 判断SYNACK的合法性,更新连接的信息。
* 把连接状态置为TCP_ESTABLISHED。
* 发送ACK,可能立即发送,也可能延迟发送。
*/
queued = tcp_rcv_synsent_state_process(sk, skb, th, len); if (queued >= 0)
return queued; /* 会导致调用函数发送RST */ tcp_urg(sk, skb, th); /* 处理紧急数据 */ /* 发送数据,并检查是否需要扩大发送缓存 */
tcp_data_snd_check(sk); return 0;
}
...
}

SYN_SENT状态处理

tcp_rcv_synsent_state_process()用于SYN_SENT状态的处理,具体又分两种场景。

(1) 接收到SYNACK

一般情况下会收到服务端的SYNACK,处理如下:

检查ack_seq是否合法。

如果使用了时间戳选项,检查回显的时间戳是否合法。

检查TCP的标志位是否合法。

如果SYNACK是合法的,更新sock的各种信息。

把连接的状态设置为TCP_ESTABLISHED,唤醒调用connect()的进程。

判断是马上发送ACK,还是延迟发送。

(2) 接收到SYN

本端之前发送出一个SYN,现在又接收到了一个SYN,双方同时向对端发起建立连接的请求。

处理如下:

把连接状态置为SYN_RECV。

更新sock的各种信息。

构造和发送SYNACK。

接者对端也会回应SYNACK,之后的处理流程和服务器端接收ACK类似,可参考之前的blog。

当tcp_rcv_synsent_state_process()的返回值大于0时,会导致上层调用函数发送一个被动的RST。

Q:那么什么情况下此函数的返回值会大于0?

A:收到一个ACK段,但ack_seq的序号不正确,或者回显的时间戳不正确。

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, unsigned int len)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_fastopen_cookie foc = { .len = -1 };
int saved_clamp = tp->rx_opt.mss_clamp; /* 全面解析skb携带的TCP选项 */
tcp_parse_options(skb, &tp->rx_opt, 0, &foc);
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
tp->rx_opt.rcv_tsecr -= tp->tsoffset; /* timestamp offset */ /* 如果携带ACK标志,那么有可能是SYNACK */
if (th->ack) {
/* rfc793:
* If the state is SYN-SENT then first check the ACK bit
* If the ACK bit is set
* If the SEG.ACK <= ISS, or SEG.ACK > SND.NXT, send
* a reset (unless the RST bit is set, if so drop the segment
* and return)"
*/
/* 检查ack_seq:snd_una < ack_seq <= snd_nxt。
* 如果SYN段没有携带数据,那么此时ack_seq应该为本端的ISN + 1。
*/
if (! after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) ||
after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))
goto reset_and_undo; /* 如果使用了时间戳选项,那么回显的时间戳,必须落在
* 第一次发送SYN段的时间和当前时间之间。
*/
if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
!between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp, tcp_time_stamp)) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSACTIVEREJECTED);
goto reset_and_undo;
} /* Now ACK is acceptable.
* If the RST bit is set
* If the ACK was acceptable then signal the user "error: connection reset",
* drop the segment, enter CLOSED state, delete TCB, and return."
*/
if (th->rst) { /* 如果携带了RST标志位,那么建立连接失败了:)*/
tcp_reset(sk);
goto discard;
} /* RFC793:
* fifth, if neither of the SYN or RST bits is set then drop the segment and return.
*/
/* 如果既没有RST也没有SYN标志位,那么直接丢弃这个ACK */
if (! th->syn)
goto discard_and_undo; /* RFC793:
* If the SYN bit is on ...
* are acceptable then ...
* (ousr SYN has been ACKed), change the connection state to ESTABLISHED...
*/
/* 收到一个合法的SYNACK了,接下来要完成连接的建立了 */ /* 如果对端支持ECN,SYNACK只会设置ECE标志。
* 否则,连接就不支持ECN显式拥塞通知了。
*/
TCP_ECN_rcv_synack(tp, th); /* 记录最近更新发送窗口的ACK序号 */
tcp_init_wl(tp, TCP_SKB_CB(skb)->seq); /* 更新发送窗口,删除发送队列中已被确认的SYN段,并进行时延采样 */
tcp_ack(sk, skb, FLAG_SLOWPATH); /* Ok. it's good. Set up sequence numbers and move to established. */
tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; /* 更新接收窗口的要接收的下一个序号 */
tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; /* 更新接收窗口的左端 */ /* RFC1323: The window in SYN & SYN/ACK segments is never scaled.
* 更新对端接收窗口的大小。在三次握手时,不使用窗口扩大因子。
*/
tp->snd_wnd = ntohs(th->window); /* 如果连接不支持窗口扩大因子选项 */
if (! tp->rx_opt.wscale_ok) {
tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0;
tp->window_clamp = min(tp->window_clamp, 65535U);
} /* 如果连接支持时间戳选项 */
if (tp->rx_opt.saw_tstamp) {
tp->rx_opt.tstamp_ok = 1;
tp->tcp_header_len = sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;
tcp_store_ts_recent(tp); /* 记录对端的时间戳,作为下次发送的回显值 */
} else {
tp->tcp_header_len = sizeof(struct tcphdr);
} /* 使用SACK时,才能考虑是否使用FACK */
if (tcp_is_sack(tp) && sysctl_tcp_fack)
tcp_enable_fack(tp); tcp_mtu_init(sk); /* TCP的MTU初始化 */
tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); /* 更新MSS */
tcp_initialize_rcv_mss(sk); /* 对端有效发送MSS估值的初始化 */ /* Remember, tcp_poll() does not lock socket!
* Change state from SYN-SENT only after copied_seq is initialized.
*/
tp->copied_seq = tp->rcv_nxt; /* 更新未读数据的左端 */ smp_mb(); /* 走到这里,连接算是成功建立了,接下来:
* 把连接的状态设置为TCP_ESTABLISHED。
* 唤醒调用connect()的进程。
*/
tcp_finish_connect(sk, skb); /* Fast Open选项处理 */
if ((tp->syn_fastopen || tp->syn_data) &&
tcp_rcv_fastopen_synack(sk, skb, &foc))
return -1; /* 符合以下任一条件,则使用延迟确认,不会马上发送ACK:
* 目前有数据等待发送。
* 使用TCP_DEFER_ACCEPT选项。
* 延迟确认标志为1。
*/
if (sk->sk_write_pending || icsk->icsk_accept_queue->rskq_defer_accept ||
icsk->icsk_ack.pingpong) {
inet_csk_schedule_ack(sk); /* 设置ICSK_ACK_SCHED标志位,表示有ACK需要发送 */
icsk->icsk_ack.lrcvtime = tcp_time_stamp; /* 更新最后一次接收到数据报的时间 */
tcp_enter_quickack_mode(sk); /* 进入快速确认模式,之后会进行快速确认 */ /* 激活延迟确认定时器,超时时间为200ms,也就是说最多延迟200ms */
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, TCP_DELACK_MAX, TCP_RTO_MAX); discard:
__kfree_skb(skb);
return 0; } else {
tcp_send_ack(sk); /* 立即发送一个ACK,即三次握手的最后一个ACK */
} return -1;
} /* No ACK in the segment */ /* 如果收到的段没有ACK标志,却设置了RST标志,那么直接丢掉 */
if (th->rst) {
/* rfc793:
* If the RST bit is set and no ACK, drop the segment and return.
*/
goto discard_and_undo;
} /* PAWS check. 检查时间戳是否合法 */
if (tp->rx_opt.ts_recent_tstamp && tp->rx_opt.saw_tstamp &&
tcp_paws_reject(&tp->rx_opt, 0))
goto discard_and_undo; /* 收到了SYN段,即同时打开 */
if (th->syn) { /* We see SYN without ACK. It is attempt of simultaneous connect
* with crossed SYNs. Particularly, it can be connect to self.
*/
/* 发送SYN后,状态为SYN_SENT,如果此时也收到SYN,
* 状态则变为SYN_RECV。
*/
tcp_set_state(sk, TCP_SYN_RECV); if (tp->rx_opt.saw_tstamp) {
tp->rx_opt.tstamp_ok = 1;
tcp_store_ts_recent(tp); /* 记录对端的时间戳,作为下次发送的回显值 */
tp->tcp_header_len = sizeof(tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
} else {
tp->tcp_header_len = sizeof(struct tcphdr);
} tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; /* 更新接收窗口的要接收的下一个序号 */
tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1; /* 更新接收窗口的左端 */ /* RFC1323: The window in SYN & SYN/ACK segments is never scaled.
* 更新对端接收窗口的大小。在三次握手时,不使用窗口扩大因子。
*/
tp->snd_wnd = ntohs(th->window);
tp->snd_wl1 = TCP_SKB_CB(skb)->seq; /* 记录最近更新发送窗口的ACK序号 */
tp->max_window = tp->snd_wnd; /* 目前见过的对端的最大通告窗口 */ /* 如果对端支持ECN,SYN会同时设置ECE和CWR标志。
* 否则,连接就不支持ECN显式拥塞通知了。
*/
TCP_ECN_rcv_syn(tp, th); tcp_mtu_init(sk); /* TCP的MTU初始化 */
tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); /* 更新MSS */
tcp_initialize_rcv_mss(sk); /* 对端有效发送MSS估值的初始化 */ /* 构造和发送SYNACK */
tcp_send_synack(sk); goto discard;
} discard_and_undo:
tcp_clear_options(&tp->rx_opt);
tp->rx_opt.mss_clamp = saved_clamp;
goto discard; reset_and_undo:
tcp_clear_options(&tp->rx_opt);
tp->rx_opt.mss_clamp = saved_clamp;
return 1;
}

同时打开时,在SYN_SENT状态,收到SYN段后,状态变为SYN_RECV,然后发送SYNACK。

之后如果收到合法的SYNACK后,就能完成连接的建立。

/* Send a crossed SYN-ACK during socket establishment.
* WARNING: This routine must only be called when we have already
* sent a SYN packet that crossed the incoming SYN that caused this
* routine to get called. If this assumption fails then the initial rcv_wnd
* and rcv_wscale values will not be correct.
*/ int tcp_send_synack(struct sock *sk)
{
struct sk_buff *skb; skb = tcp_write_queue_head(sk); /* 发送队列的第一个段,即SYN段 */ if (skb == NULL || ! (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_SYN)) {
pr_debug("%s: wrong queue state\n", __func__);
return -EFAULT;
} if (! (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_ACK)) { /* 如果这个skb是克隆的,并且有多个使用者,那么就不能直接修改此skb。
* 此时再克隆一个私有的nskb,替换掉之前的。然后就可以任意修改了。
*/
if (skb_cloned(skb)) {
struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC); /* 再克隆一份 */
if (nskb == NULL)
return -ENOMEM; tcp_unlink_write_queue(skb, sk); /* 把skb从发送队列中删除 */
skb_header_release(nskb); /* 增加skb负荷部分的引用计数 */
__tcp_add_write_queue_head(sk, nskb); /* 把nskb放入发送队列的头部 */
sk_wmem_free_skb(sk, skb); /* 更新内存使用情况 */
sk->sk_wmem_queued += nskb->truesize; /* 更新发送队列的总大小 */
sk_mem_charge(sk, nskb->truesize); /* 更新预分配但未使用的内存大小 */
skb = nskb; /* 接下来使用的是独占的nskb */
} TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_ACK;
TCP_ECN_send_synack(tcp_sk(sk), skb); /* 设置ECN标志位 */
} TCP_SKB_CB(skb)->when = tcp_time_stamp;
return tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC); /* 发送此SYNACK段 */
} static inline void sk_wmem_free_skb(struct sock *sk, struct sk_buff *skb)
{
/* write queue has been shrunk recently */
sock_set_flag(sk, SOCK_QUEUE_SHRUNK);
sk->sk_wmem_queued -= skb->truesize; /* 更新发送队列的总大小 */
sk_mem_uncharge(sk, skb->truesize); /* 更新预分配但未使用的内存大小 */
__kfree_skb(skb);
}

唤醒用户进程

tcp_finish_connect()用来完成连接的建立,主要做了以下事情:

1. 把连接的状态从SYN_SENT置为ESTABLISHED。

2. 根据路由缓存,初始化TCP相关的变量。

3. 获取默认的拥塞控制算法。

4. 调整发送缓存和接收缓存的大小。

5. 如果使用了SO_KEEPALIVE选项,激活保活定时器。

6. 唤醒此socket等待队列上的进程(即调用connect的进程)。

如果使用了异步通知,则发送SIGIO通知异步通知队列上的进程可写。

void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk); /* 连接状态从SYN_SENT变为ESTABLISHED */
tcp_set_state(sk, TCP_ESTABLISHED); if (skb != NULL) {
icsk->icsk_af_ops->sk_rx_dst_set(sk, skb);
security_inet_conn_established(sk, skb);
} /* Make sure socket is routed, for correct metrics */
icsk->icsk_af_ops->rebuild_header(sk); /* 根据路由缓存,初始化TCP相关变量 */
tcp_init_metrics(sk); /* 获取默认的TCP拥塞控制算法 */
tcp_init_congestion_control(sk); /* Prevent spurious tcp_cwnd_restart() on first data packet. */
tp->lsndtime = tcp_time_stamp; /* 最近发包的时间 */ /* 调整发送缓存和接收缓存的大小 */
tcp_init_buffer_space(sk); /* 如果使用了SO_KEEPALIVE选项,激活保活定时器 */
if (sock_flag(sk, SOCK_KEEPOPEN))
inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp)); /* 如果对端的窗口扩大因子为0 */
if (! tp->rx_opt.snd_wscale)
__tcp_fast_path_on(tp, tp->snd_wnd); /* 设置首部预测字段 */
else
tp->pred_flags = 0; if (! sock_flag(sk, SOCK_DEAD)) {
/* 指向sock_def_wakeup,唤醒调用connect()的进程 */
sk->sk_state_change(sk); /* 如果使用了异步通知,则发送SIGIO通知进程可写 */
sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
}
}

TCP连接建立系列 — 客户端接收SYNACK和发送ACK的更多相关文章

  1. TCP连接建立系列 — 客户端发送SYN段

    主要内容:客户端调用connect()时的TCP层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd connect的TCP层实现 SOCK_STRE ...

  2. TCP连接建立系列 — 客户端的端口选取和重用

    主要内容:connect()时的端口选取和端口重用. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 端口选取 connect()时本地端口是如何选取的呢 ...

  3. TCP连接建立系列 — 服务端发送SYNACK段

    本文主要分析:服务器端如何构造和发送SYNACK段. 内核版本:3.6 Author:zhangskd @ csdn blog 发送入口 tcp_v4_send_synack()用于发送SYNACK段 ...

  4. TCP连接建立系列 — 服务端接收ACK段(一)

      http://blog.csdn.net/zhangskd/article/details/17923917 分类: Linux TCP/IP Linux Kernel 2014-01-07 09 ...

  5. TCP连接建立系列 — 服务端接收SYN段

    本文主要分析:服务器端接收到SYN包时的处理路径. 内核版本:3.6 Author:zhangskd @ csdn blog 接收入口 1. 状态为ESTABLISHED时,用tcp_rcv_esta ...

  6. TCP连接建立系列 — 服务端接收ACK段(二)

    本文主要分析:三次握手中最后一个ACK段到达时,服务器端的处理路径. 内核版本:3.6 Author:zhangskd @ csdn blog 创建新sock 协议族相关的操作函数,我们要看的是TCP ...

  7. TCP连接建立系列 — 连接请求块

    连接请求块(request_sock)之于TCP三次握手,就如同网络数据包(sk_buff)之于网络协议栈,都是核心的数据结构. 内核版本:3.6 Author:zhangskd @ csdn blo ...

  8. TCP连接建立系列 — TCP选项解析

    本文主要分析:在收到客户端的SYN包时,服务器端是如何解析它所携带的TCP选项,并结合本端情况决定是否予以支持. 内核版本:3.6 Author:zhangskd @ csdn blog 概述 收到客 ...

  9. 运输层协议:TCP连接建立与释放

    TCP的特点 面向连接:TCP是面向连接的运输层协议,通过TCP发送数据需要先建立连接,通信结束后需要释放连接 可靠传输:TCP实现了可靠传输,使得数据能够无丢失.无差错.不重复地到达接收端 面向字节 ...

随机推荐

  1. Codeforces 666E Forensic Examination

    题意:给定主串s和m个模式串,每次询问[l,r]的模式串中出现s[pl...pr]次数最多的串和次数. 这题挺简单的,先把所有模式串拿来建广义后缀自动机,询问相当于子树众数,用线段树合并即可. 那我为 ...

  2. Java并发编程:JMM(Java内存模型)和volatile

    1. 并发编程的3个概念 并发编程时,要想并发程序正确地执行,必须要保证原子性.可见性和有序性.只要有一个没有被保证,就有可能会导致程序运行不正确. 1.1. 原子性 原子性:即一个或多个操作要么全部 ...

  3. Centos7发送邮件

    Centos7发送邮件 $ yum -y install mailx sendmail $ vim /etc/mail.rc set from=xxxxxx@.com set smtp=smtp..c ...

  4. jQuery简介和基础

    一.函数变量的作用域 1.变量的作用域实在声明时决定的而不是调用执行时决定 <script> var a=6,b=7; function t() { // var a=3,b=5; con ...

  5. python dataframe数据条件筛选

    一般情况下我们从一堆数据中选择我们获取想要的数据会通过一下方式: (1)创建链表或数组: (2)用for 循环遍历所有数据,将想要的存入链表或数组. 但是python中我们不需要这么做,我们可以用Pa ...

  6. 12_Python的(匿名函数)Lambda表达式_Python编程之路

    Python作为一门高级语言,与很多编程语言一样都具有匿名函数这一特征 匿名函数,也就Lambda表达式,通俗来讲就是不用命名的方法,直接定义,直接用即可 创建匿名函数需要用到Lambda关键字,下面 ...

  7. 如何理解主函数main中变量(int argc,char *argv[])的含义

    每一个C语言的初学者,都会注意到主函数main()里的两个参数,但是初学者一般不会去关注这两个参数的具体作用,下面我们就来介绍这两个参数的具体作用. main()函数是控制台程序的入口,int mai ...

  8. python中的缩进问题

    python中没有{}来表示代码块,而是用缩进来表示,刚开始写python代码,没有注意缩进,结果各种报错(( ╯□╰ )). 在python中的原则就是同一层次的代码一定要有相同的缩进!!! 从上图 ...

  9. div英文内容超过div长度

    添加css word-break: normal;word-wrap: break-word;

  10. 服务器&阵列卡&组raid 5

    清除raid信息后,机器将会读不到系统, 后面若进一步操作处理, raid信息有可能会被初始化掉,那么硬盘数据就有可能会被清空, 导致数据丢失, 否则如果只是清除raid信息,重做raid是可以还原系 ...