RAID5 IO处理之写请求代码详解
我们知道RAID5一个条带上的数据是由N个数据块和1个校验块组成,其校验块由N个数据块通过异或运算得出,这样才能在任意一个成员磁盘失效时通过其他N个成员磁盘恢复出用户写入的数据。这也就要求RAID5条带上的数据是一致的、同步的。
1 写入方式
当新数据写入时就需要重新计算校验值,计算方式由以下两种:
- 将条带上没有写请求的位置的数据读出,然后使用新数据和旧数据两者重新计算校验
- 将条带上将要写数据的位置的数据和校验数据读出,然后试用新数据、旧数据和旧校验三者重新计算校验
我们以5块盘创建的RAID5为例,其每个条带上共有4个数据块和1个校验块。假设某个条带上的数据块依次为D1、D2、D3、D4,校验块为P1,在条带一致的情况下:P1 = D1 ⊕ D2 ⊕ D3 ⊕ D4。现在新的数据D1'要覆盖原来的D1,两种写入方式的操作如下:
第一种方式:先读出D2、D3、D4,再加上要写入的D1'计算出P1',即:P1' = D2 ⊕ D3 ⊕ D4 ⊕ D1'
第二种方式:先读出D1、P1,再加上要写入的D1'计算出P1',即:P1' = D1 ⊕ P1 ⊕ D1'
从具体的例子我们可以看出两种方式的本质区别在于要读出旧数据的个数不同,读的越少性能越好。因此在处理逻辑中会先计算那种方式需要读出的数据更少,然后采用该种写方式。第一种读其他旧数据直接计算新校验的写方式称为 重构写 ,第二种读旧数据和旧校验的方式称为 读改写 。
2 读改写
我们以5块盘chunk为4K的RAID5为例。按照前文的分析,如果我们在RAID5的起始位置写入大小为4K的IO,此时会采用读改写的方式下发请求。代码分析如下:
2.1 接收请求
函数调用关系:
raid5_make_request()
\_ add_stripe_bio()
\_ raid5_release_stripe()
\_ md_wakeup_thread()
接收到请求后,在 raid5_make_request()
按4K大小切分bio,通过 add_stripe_bio()
将bio挂在到条带上,推入条带状态机进行处理。
2.2 下发读请求
函数调用关系:
handle_stripe()
\_ analyse_stripe()
\_ handle_stripe_dirtying()
\_ ops_run_io()
代码解析:
static void handle_stripe(struct stripe_head *sh)
{
/* 解析条带状态 */
analyse_stripe(sh, &s);
/* to_write条件成立设置需要读数据的条带/设备 */
if (s.to_write && !sh->reconstruct_state && !sh->check_state)
handle_stripe_dirtying(conf, sh, &s, disks);
/* 下发读请求 */
ops_run_io(sh, &s);
}
static void analyse_stripe(struct stripe_head *sh, struct stripe_head_state *s)
{
rcu_read_lock();
/* 遍历所有条带/设备 */
for (i = disks; i--; ) {
dev = &sh->dev[i];
/* 写请求挂到条带设备的towrite上此时为真 */
if (dev->towrite) {
s->to_write++;
/*
* 条带头处理数据块的大小为4K,与内核page大小一致
* 因此如果写的数据不足4K时需要先将原来的4K数据读出来再用新的数据覆盖
* R5_OVERWRITE标记在挂在bio时进行判断并设置
*/
if (!test_bit(R5_OVERWRITE, &dev->flags))
s->non_overwrite++;
}
/* 正常情况下成员磁盘状态正常 */
clear_bit(R5_Insync, &dev->flags);
if (test_bit(In_sync, &rdev->flags))
set_bit(R5_Insync, &dev->flags);
}
rcu_read_unlock();
}
static void handle_stripe_dirtying(struct r5conf *conf,
struct stripe_head *sh,
struct stripe_head_state *s,
int disks)
{
/*
* 以下几种情况不能进行读改写只能使用重构写
* 1.RAID级别为6,因为读改写利用的是异或运算的特性,因此RAID6不适用
* 2.IO起始范围超过了同步的进度。因为读改写需要用旧的数据和旧的校验,
* 如果旧数据见不是一致的,那么再进行异或运算数据也是不一致的,
*/
if (conf->max_degraded == 2 ||
(recovery_cp < MaxSector && sh->sector >= recovery_cp)) {
/* Calculate the real rcw later - for now make it
* look like rcw is cheaper
*/
rcw = 1; rmw = 2;
pr_debug("force RCW max_degraded=%u, recovery_cp=%llu sh->sector=%llu\n",
conf->max_degraded, (unsigned long long)recovery_cp,
(unsigned long long)sh->sector);
} else for (i = disks; i--; ) {
/* 如果dev有写请求或保存的是校验值那么该dev在读改写逻辑中是需要读的 */
struct r5dev *dev = &sh->dev[i];
if ((dev->towrite || i == sh->pd_idx) &&
!test_bit(R5_LOCKED, &dev->flags) &&
!(test_bit(R5_UPTODATE, &dev->flags) ||
test_bit(R5_Wantcompute, &dev->flags))) {
rmw++;
}
/* 如果dev非满写(为写满4K或无IO)并且不是校验那么该dev在重构写逻辑中是需要读的 */
if (!test_bit(R5_OVERWRITE, &dev->flags) && i != sh->pd_idx &&
!test_bit(R5_LOCKED, &dev->flags) &&
!(test_bit(R5_UPTODATE, &dev->flags) ||
test_bit(R5_Wantcompute, &dev->flags))) {
rcw++;
}
}
/* 进行读改写需要读的次数少 */
if (rmw < rcw && rmw > 0) {
/* 遍历所有条带/设备 */
for (i = disks; i--; ) {
struct r5dev *dev = &sh->dev[i];
/*
* 以下几种情况该dev下发读请求
* 1.有写请求或保存的是校验值
* 2.并且dev没有下发请求(R5_LOCKED)
* 3.并且dev中的数据不是磁盘中实际保存的数据(R5_UPTODATE)
* 并且没有在进行计算(R5_Wantcompute)
* 4.并且dev对应成员磁盘状态正常(R5_Insync)
*/
if ((dev->towrite || i == sh->pd_idx) &&
!test_bit(R5_LOCKED, &dev->flags) &&
!(test_bit(R5_UPTODATE, &dev->flags) ||
test_bit(R5_Wantcompute, &dev->flags)) &&
test_bit(R5_Insync, &dev->flags)) {
/* 给条带/设备上锁表明正在进行IO */
set_bit(R5_LOCKED, &dev->flags);
/* 表明该条带/设备要调度读请求 */
set_bit(R5_Wantread, &dev->flags);
/* locked增加计数 */
s->locked++;
}
}
}
/* locked不等于0本轮不调度schedule_reconstruction */
if ((s->req_compute || !test_bit(STRIPE_COMPUTE_RUN, &sh->state)) &&
(s->locked == 0 && (rcw == 0 || rmw == 0) &&
!test_bit(STRIPE_BIT_DELAY, &sh->state)))
schedule_reconstruction(sh, s, rcw == 0, 0);
}
static void ops_run_io(struct stripe_head *sh, struct stripe_head_state *s)
{
/* 遍历所有条带/设备 */
for (i = disks; i--; ) {
/* 对设置了读标记的下发读请求 */
if (test_and_clear_bit(R5_Wantread, &sh->dev[i].flags))
rw = READ;
/* 跳过其他不需要读的设备 */
else
continue;
if (rdev) {
bio_reset(bi);
bi->bi_bdev = rdev->bdev;
bi->bi_rw = rw;
bi->bi_end_io = raid5_end_read_request;
bi->bi_private = sh;
atomic_inc(&sh->count);
if (use_new_offset(conf, sh))
bi->bi_sector = (sh->sector + rdev->new_data_offset);
else
bi->bi_sector = (sh->sector + rdev->data_offset);
if (test_bit(R5_ReadNoMerge, &sh->dev[i].flags))
bi->bi_rw |= REQ_FLUSH;
bi->bi_vcnt = 1;
bi->bi_io_vec[0].bv_len = STRIPE_SIZE;
bi->bi_io_vec[0].bv_offset = 0;
bi->bi_size = STRIPE_SIZE;
/* 提交bio */
generic_make_request(bi);
}
}
}
2.3 计算新校验
函数调用关系:
raid5_end_read_request()
\_ handle_stripe()
\_ analyse_stripe()
\_ handle_stripe_dirtying()
\_ schedule_reconstruction()
\_ raid_run_ops()
\_ ops_run_prexor5()
\_ ops_run_biodrain()
\_ ops_run_reconstruct5()
由上轮次下发读请求的回调出发本轮次处理。
本轮次中,再次进入 handle_stripe_dirtying()
后因为读请求的完成,上轮次中需要读的条带/设备都设置了 R5_UPTODATE 标记,所以一方面 rmw 变量等于0,另一方面在不需要下发请求 s->locked 等于0,因此满足条件进入到 schedule_reconstruction()
中。
static void
schedule_reconstruction(struct stripe_head *sh, struct stripe_head_state *s,
int rcw, int expand)
{
if (!rcw) {
/* RAID6不支持读改写 */
BUG_ON(level == 6);
BUG_ON(!(test_bit(R5_UPTODATE, &sh->dev[pd_idx].flags) ||
test_bit(R5_Wantcompute, &sh->dev[pd_idx].flags)));
/* 遍历所有条带/设备 */
for (i = disks; i--; ) {
struct r5dev *dev = &sh->dev[i];
/* 跳过校验 */
if (i == pd_idx)
continue;
/* 有写请求的条带/设备设置相关标记 */
if (dev->towrite &&
(test_bit(R5_UPTODATE, &dev->flags) ||
test_bit(R5_Wantcompute, &dev->flags))) {
/* 将数据从bio中拷贝到dev->page中 */
set_bit(R5_Wantdrain, &dev->flags);
/* 给条带/设备上锁表明正在进行IO */
set_bit(R5_LOCKED, &dev->flags);
/* 清除标记表明当前条带/设备的page中的数据不可直接试用 */
clear_bit(R5_UPTODATE, &dev->flags);
/* locked计数 */
s->locked++;
}
}
/* 设置条带重构状态 */
sh->reconstruct_state = reconstruct_state_prexor_drain_run;
/* 设置条带需要进行异或运算 */
set_bit(STRIPE_OP_PREXOR, &s->ops_request);
/* 设置条带需要“抽干”数据 */
set_bit(STRIPE_OP_BIODRAIN, &s->ops_request);
/* 设置条带需要计算校验 */
set_bit(STRIPE_OP_RECONSTRUCT, &s->ops_request);
}
/* 给校验值所在条带/设备上锁表明正在进行IO */
set_bit(R5_LOCKED, &sh->dev[pd_idx].flags);
/* 清除标记表明当前条带/设备的page中的数据不可直接试用 */
clear_bit(R5_UPTODATE, &sh->dev[pd_idx].flags);
/* locked计数 */
s->locked++;
}
static void raid_run_ops(struct stripe_head *sh, unsigned long ops_request)
{
/* 先使用旧数据和旧校验进行异或运算获得中间状态的校验 */
if (test_bit(STRIPE_OP_PREXOR, &ops_request))
tx = ops_run_prexor(sh, percpu, tx);
/* 将新数据拷贝从bio中拷贝到dev中 */
if (test_bit(STRIPE_OP_BIODRAIN, &ops_request))
tx = ops_run_biodrain(sh, tx);
/* 计算最终的校验值 */
if (test_bit(STRIPE_OP_RECONSTRUCT, &ops_request)) {
if (level < 6)
ops_run_reconstruct5(sh, percpu, tx);
else
ops_run_reconstruct6(sh, percpu, tx);
}
}
static struct dma_async_tx_descriptor *
ops_run_prexor(struct stripe_head *sh, struct raid5_percpu *percpu,
struct dma_async_tx_descriptor *tx)
{
/* 将校验值的page设置为第一个源数据和目标数据 */
struct page *xor_dest = xor_srcs[count++] = sh->dev[pd_idx].page;
/* 遍历所有条带/设备 */
for (i = disks; i--; ) {
struct r5dev *dev = &sh->dev[i];
/* 需要“抽干”数据的dev即包含新数据的dev,将其page依次设置为源数据 */
if (test_bit(R5_Wantdrain, &dev->flags))
xor_srcs[count++] = dev->page;
}
/* 进行异或运算 */
init_async_submit(&submit, ASYNC_TX_FENCE|ASYNC_TX_XOR_DROP_DST, tx,
ops_complete_prexor, sh, to_addr_conv(sh, percpu));
tx = async_xor(xor_dest, xor_srcs, 0, count, STRIPE_SIZE, &submit);
return tx;
}
static struct dma_async_tx_descriptor *
ops_run_biodrain(struct stripe_head *sh, struct dma_async_tx_descriptor *tx)
{
/* 遍历所有条带/设备 */
for (i = disks; i--; ) {
/* 处理所有需要“抽干”数据的dev */
if (test_and_clear_bit(R5_Wantdrain, &dev->flags)) {
struct bio *wbi;
spin_lock_irq(&sh->stripe_lock);
/* 将bio从towrite转移到written表明开始调度 */
chosen = dev->towrite;
dev->towrite = NULL;
BUG_ON(dev->written);
wbi = dev->written = chosen;
spin_unlock_irq(&sh->stripe_lock);
/* 将bio中本条带范围内的所有数据拷贝到dev的page中 */
while (wbi && wbi->bi_sector < dev->sector + STRIPE_SECTORS) {
tx = async_copy_data(1, wbi, dev->page, dev->sector, tx);
wbi = r5_next_bio(wbi, dev->sector);
}
}
}
return tx;
}
static void
ops_run_reconstruct5(struct stripe_head *sh, struct raid5_percpu *percpu,
struct dma_async_tx_descriptor *tx)
{
/* check if prexor is active which means only process blocks
* that are part of a read-modify-write (written)
*/
if (sh->reconstruct_state == reconstruct_state_prexor_drain_run) {
prexor = 1;
/* 将校验值的page设置为第一个源数据和目标数据 */
xor_dest = xor_srcs[count++] = sh->dev[pd_idx].page;
/* 所有包含需要写请求的条带/设备依次设置为源数据 */
for (i = disks; i--; ) {
struct r5dev *dev = &sh->dev[i];
if (dev->written)
xor_srcs[count++] = dev->page;
}
}
/* 1/ if we prexor'd then the dest is reused as a source
* 2/ if we did not prexor then we are redoing the parity
* set ASYNC_TX_XOR_DROP_DST and ASYNC_TX_XOR_ZERO_DST
* for the synchronous xor case
*/
flags = ASYNC_TX_ACK |
(prexor ? ASYNC_TX_XOR_DROP_DST : ASYNC_TX_XOR_ZERO_DST);
atomic_inc(&sh->count);
/* 进行异步异或运算,完成后进入回调函数ops_complete_reconstruct */
init_async_submit(&submit, flags, tx, ops_complete_reconstruct, sh, to_addr_conv(sh, percpu));
tx = async_xor(xor_dest, xor_srcs, 0, count, STRIPE_SIZE, &submit);
}
2.4 下发写请求
函数调用关系:
ops_complete_reconstruct()
\_ handle_stripe()
\_ analyse_stripe()
\_ ops_run_io()
代码逻辑如下:
static void ops_complete_reconstruct(void *stripe_head_ref)
{
/* 遍历所有条带/设备 */
for (i = disks; i--; ) {
/* 普通写请求没有如下标记 */
fua |= test_bit(R5_WantFUA, &sh->dev[i].flags);
sync |= test_bit(R5_SyncIO, &sh->dev[i].flags);
discard |= test_bit(R5_Discard, &sh->dev[i].flags);
}
for (i = disks; i--; ) {
struct r5dev *dev = &sh->dev[i];
if (dev->written || i == pd_idx || i == qd_idx) {
if (!discard)
/* 设置R5_UPTODATE标记表明条带/设备中的数据为最新可用的 */
set_bit(R5_UPTODATE, &dev->flags);
}
}
/* 设置条带重构状态 */
if (sh->reconstruct_state == reconstruct_state_prexor_drain_run)
sh->reconstruct_state = reconstruct_state_prexor_drain_result;
/* 将条带推入状态机 */
set_bit(STRIPE_HANDLE, &sh->state);
release_stripe(sh);
}
static void handle_stripe(struct stripe_head *sh)
{
/* 解析条带状态 */
analyse_stripe(sh, &s);
prexor = 0;
/* 条件成立 */
if (sh->reconstruct_state == reconstruct_state_prexor_drain_result)
prexor = 1;
if (sh->reconstruct_state == reconstruct_state_drain_result ||
sh->reconstruct_state == reconstruct_state_prexor_drain_result) {
sh->reconstruct_state = reconstruct_state_idle;
for (i = disks; i--; ) {
struct r5dev *dev = &sh->dev[i];
/* 所有设置R5_LOCKED的条带/设备如果其是校验或有写请求 */
if (test_bit(R5_LOCKED, &dev->flags) &&
(i == sh->pd_idx || i == sh->qd_idx ||
dev->written)) {
/* 为条带/设备设置R5_Wantwrite标记表明需要下发写请求 */
set_bit(R5_Wantwrite, &dev->flags);
/* 读改写跳过下面重构写的相关逻辑判断 */
if (prexor)
continue;
/* 如果是重构写此时条带处于一致状态设置相关标记 */
if (!test_bit(R5_Insync, &dev->flags) ||
((i == sh->pd_idx || i == sh->qd_idx) &&
s.failed == 0))
set_bit(STRIPE_INSYNC, &sh->state);
}
}
}
/* 下发写请求 */
ops_run_io(sh, &s);
}
static void ops_run_io(struct stripe_head *sh, struct stripe_head_state *s)
{
/* 遍历所有条带/设备 */
for (i = disks; i--; ) {
/* 所有设置了R5_Wantwrite标记的条带/设备下发写请求 */
if (test_and_clear_bit(R5_Wantwrite, &sh->dev[i].flags)) {
rw = WRITE;
} else
continue;
rcu_read_lock();
rdev = rcu_dereference(conf->disks[i].rdev);
rcu_read_unlock();
if (rdev) {
bio_reset(bi);
bi->bi_bdev = rdev->bdev;
bi->bi_rw = rw;
bi->bi_end_io = raid5_end_write_request;
bi->bi_private = sh;
atomic_inc(&sh->count);
if (use_new_offset(conf, sh))
bi->bi_sector = (sh->sector + rdev->new_data_offset);
else
bi->bi_sector = (sh->sector + rdev->data_offset);
if (test_bit(R5_ReadNoMerge, &sh->dev[i].flags))
bi->bi_rw |= REQ_FLUSH;
bi->bi_vcnt = 1;
bi->bi_io_vec[0].bv_len = STRIPE_SIZE;
bi->bi_io_vec[0].bv_offset = 0;
bi->bi_size = STRIPE_SIZE;
/* 提交写请求 */
generic_make_request(bi);
}
}
}
2.5 向上返回
函数调用关系:
raid5_end_write_request()
\_ handle_stripe()
\_ analyse_stripe()
\_ handle_stripe_clean_event()
代码逻辑如下:
static void handle_stripe(struct stripe_head *sh)
{
/* 解析条带状态,本轮次最主要设置s->written计数 */
analyse_stripe(sh, &s);
/* 有已经完成调度的写且校验值已经更新完成说明IO整体完成 */
pdev = &sh->dev[sh->pd_idx];
if (s.written &&
(s.p_failed || ((test_bit(R5_Insync, &pdev->flags)
&& !test_bit(R5_LOCKED, &pdev->flags)
&& (test_bit(R5_UPTODATE, &pdev->flags) ||
test_bit(R5_Discard, &pdev->flags))))))
/* 将写完成的IO保存到return_bi中 */
handle_stripe_clean_event(conf, sh, disks, &s.return_bi);
/* 向上返回 */
return_io(s.return_bi);
}
static void handle_stripe_clean_event(struct r5conf *conf,
struct stripe_head *sh, int disks, struct bio **return_bi)
{
/* 遍历所有条带/设备 */
for (i = disks; i--; )
/* 处理所有已调度写请求的条带/设备 */
if (sh->dev[i].written) {
dev = &sh->dev[i];
/*
* 无R5_LOCKED标记表明IO处理结束
* 有R5_UPTODATE标记表明IO处理成功
*/
if (!test_bit(R5_LOCKED, &dev->flags) &&
(test_bit(R5_UPTODATE, &dev->flags) ||
test_bit(R5_Discard, &dev->flags))) {
/* We can return any write requests */
struct bio *wbi, *wbi2;
/* 将所有处理完成的bio挂载到return_bi中 */
wbi = dev->written;
dev->written = NULL;
while (wbi && wbi->bi_sector <
dev->sector + STRIPE_SECTORS) {
wbi2 = r5_next_bio(wbi, dev->sector);
if (!raid5_dec_bi_active_stripes(wbi)) {
md_write_end(conf->mddev);
wbi->bi_next = *return_bi;
*return_bi = wbi;
}
wbi = wbi2;
}
}
}
}
static void return_io(struct bio *return_bi)
{
struct bio *bi = return_bi;
/* 通过bi_next遍历bio调用bio_endio结束io并通知上层 */
while (bi) {
return_bi = bi->bi_next;
bi->bi_next = NULL;
bi->bi_size = 0;
bio_endio(bi, 0);
bi = return_bi;
}
}
以上,读改写处理结束。
3 重构写
重构写整体逻辑与读改写相差不大,我们只将不相同的部分说明如下,整体逻辑大家可自行串联。
第一轮读数据:
static void handle_stripe_dirtying(struct r5conf *conf,
struct stripe_head *sh,
struct stripe_head_state *s,
int disks)
{
int rmw = 0, rcw = 0, i;
sector_t recovery_cp = conf->mddev->recovery_cp;
/*
* 以下几种情况不能进行读改写只能使用重构写
* 1.RAID级别为6,因为读改写利用的是异或运算的特性,因此RAID6不适用
* 2.IO起始范围超过了同步的进度。因为读改写需要用旧的数据和旧的校验,
* 如果旧数据见不是一致的,那么再进行异或运算数据也是不一致的,
*/
if (conf->max_degraded == 2 ||
(recovery_cp < MaxSector && sh->sector >= recovery_cp)) {
/* Calculate the real rcw later - for now make it
* look like rcw is cheaper
*/
rcw = 1; rmw = 2;
pr_debug("force RCW max_degraded=%u, recovery_cp=%llu sh->sector=%llu\n",
conf->max_degraded, (unsigned long long)recovery_cp,
(unsigned long long)sh->sector);
} else for (i = disks; i--; ) {
/* 如果dev有写请求或保存的是校验值那么该dev在读改写逻辑中是需要读的 */
struct r5dev *dev = &sh->dev[i];
if ((dev->towrite || i == sh->pd_idx) &&
!test_bit(R5_LOCKED, &dev->flags) &&
!(test_bit(R5_UPTODATE, &dev->flags) ||
test_bit(R5_Wantcompute, &dev->flags))) {
if (test_bit(R5_Insync, &dev->flags))
rmw++;
else
rmw += 2*disks; /* cannot read it */
}
/* 如果dev非满写(为写满4K或无IO)并且不是校验那么该dev在重构写逻辑中是需要读的 */
if (!test_bit(R5_OVERWRITE, &dev->flags) && i != sh->pd_idx &&
!test_bit(R5_LOCKED, &dev->flags) &&
!(test_bit(R5_UPTODATE, &dev->flags) ||
test_bit(R5_Wantcompute, &dev->flags))) {
if (test_bit(R5_Insync, &dev->flags)) rcw++;
else
rcw += 2*disks;
}
}
/* 进行重构写需要读的次数少 */
if (rcw <= rmw && rcw > 0) {
/* want reconstruct write, but need to get some data */
int qread =0;
rcw = 0;
for (i = disks; i--; ) {
struct r5dev *dev = &sh->dev[i];
/*
* 满足以下条件下发读请求
* 1. 非满写
* 2. 不是校验
* 3. 尚未下发请求
* 4. 当前page中不是最新数据且不需要计算
*/
if (!test_bit(R5_OVERWRITE, &dev->flags) &&
i != sh->pd_idx && i != sh->qd_idx &&
!test_bit(R5_LOCKED, &dev->flags) &&
!(test_bit(R5_UPTODATE, &dev->flags) ||
test_bit(R5_Wantcompute, &dev->flags))) {
rcw++;
if (!test_bit(R5_Insync, &dev->flags))
continue; /* it's a failed drive */
/* 给条带/设备上锁表明正在进行IO */
set_bit(R5_LOCKED, &dev->flags);
/* 表明该条带/设备要调度读请求 */
set_bit(R5_Wantread, &dev->flags);
s->locked++;
/* locked增加计数 */
qread++;
}
}
}
/* locked不等于0本轮不调度schedule_reconstruction */
if ((s->req_compute || !test_bit(STRIPE_COMPUTE_RUN, &sh->state)) &&
(s->locked == 0 && (rcw == 0 || rmw == 0) &&
!test_bit(STRIPE_BIT_DELAY, &sh->state)))
schedule_reconstruction(sh, s, rcw == 0, 0);
}
在第二轮计算校验的条带处理中,因为直接使用数据段计算校验段,因此不需要做 ops_run_prexor
,后续下发写请求和向上返回与读改写相同。
满写是重构写的一种特殊情况,即所有数据段都是满写,此时不需要读旧数据,直接根据新数据计算校验然后一起写入,此时性能最好,我们在实际使用中,可以根据业务写IO的buffer大小调整数据盘个数和chunk大小,以达到满写的状态。
RAID5 IO处理之写请求代码详解的更多相关文章
- RAID5 IO处理之条带读代码详解
除了对齐读流程中读失败通过条带重试的场景会进入到条带读,当IO覆盖范围超过一个chunk时也会进入条带读(如向chunk为4K的RAID下发起始位置为1K大小为4K的IO),接下来我们就这部分逻辑进行 ...
- RAID5 IO处理之对齐读代码详解
1 总体流程 当一个读请求的覆盖范围落在一个chunk范围内时为对齐读,流程图如下所示: 2 入口 在RAID5的IO处理函数 make_request() 一开始进行了对齐读的判断和处理,代码如下所 ...
- Github-karpathy/char-rnn代码详解
Github-karpathy/char-rnn代码详解 zoerywzhou@gmail.com http://www.cnblogs.com/swje/ 作者:Zhouwan 2016-1-10 ...
- Spring RestController 请求参数详解
Spring RestController 请求参数详解 引用作者jpfss 在阅读之前,最好先了解http请求的get,post,以及各种head头类型,请求参数类型. 无参数,设置RestCont ...
- HTTP请求方法详解
HTTP请求方法详解 请求方法:指定了客户端想对指定的资源/服务器作何种操作 下面我们介绍HTTP/1.1中可用的请求方法: [GET:获取资源] GET方法用来请求已被URI识别的资源.指定 ...
- BM算法 Boyer-Moore高质量实现代码详解与算法详解
Boyer-Moore高质量实现代码详解与算法详解 鉴于我见到对算法本身分析非常透彻的文章以及实现的非常精巧的文章,所以就转载了,本文的贡献在于将两者结合起来,方便大家了解代码实现! 算法详解转自:h ...
- 代码详解:TensorFlow Core带你探索深度神经网络“黑匣子”
来源商业新知网,原标题:代码详解:TensorFlow Core带你探索深度神经网络“黑匣子” 想学TensorFlow?先从低阶API开始吧~某种程度而言,它能够帮助我们更好地理解Tensorflo ...
- JAVA类与类之间的全部关系简述+代码详解
本文转自: https://blog.csdn.net/wq6ylg08/article/details/81092056类和类之间关系包括了 is a,has a, use a三种关系(1)is a ...
- Java中String的intern方法,javap&cfr.jar反编译,javap反编译后二进制指令代码详解,Java8常量池的位置
一个例子 public class TestString{ public static void main(String[] args){ String a = "a"; Stri ...
随机推荐
- Windows环境安装Hadoop环境
1,下载Hadoop,解压 2,配置Hadoop环境变量 右键此电脑--属性 高级系统设置 环境变量 新建一个HADOOP_HOME 添加到path 3,cmd窗口查看安装情况:hadoop vers ...
- Java中break、continue和return三者之间的区别
控制流程语句 java关键字break.continue.return主要按三个纬度去区分. 作用不同 结束不同 紧跟不同 一.作用不同 1.break:执行break操作,跳出所在的当前整个循环, ...
- Taurus.MVC WebAPI 入门开发教程3:路由类型和路由映射。
系列目录 1.Taurus.MVC WebAPI 入门开发教程1:框架下载环境配置与运行. 2.Taurus.MVC WebAPI 入门开发教程2:添加控制器输出Hello World. 3.Tau ...
- Linux入门操作介绍
Linux 是由unix衍生而来(小知识:mac也是使用unix核心),由全世界的程序员一起开发的开源系统.如今基于linux已经有了很多版本,我们后面使用的就是衍生版本之一的Ubuntu. Ubun ...
- Bellman-Ford算法与SPFA算法详解
PS:如果您只需要Bellman-Ford/SPFA/判负环模板,请到相应的模板部分 上一篇中简单讲解了用于多源最短路的Floyd算法.本篇要介绍的则是用与单源最短路的Bellman-Ford算法和它 ...
- CF 559C - Gerald and Giant Chess (组合计数)
\(C_{x+y}^y\)的公式,DP容斥删多余贡献. #include <cstdio> #include <iostream> #include <cstring&g ...
- .NET 7 发布的最后一个预览版Preview 7, 下个月发布RC
微软在2022年8月9日 发布了.NET 7 Preview 7[1],这是它在11月10日 RTM 之前进入发布候选阶段之前的最后预览版. 预览版 7 已在 Visual Studio 17.4 预 ...
- DBPack 限流熔断功能发布说明
上周我们发布了 v0.4.0 版本,增加了限流熔断功能,现对这两个功能做如下说明. 限流 DBPack 限流熔断功能通过 filter 实现.要设置限流规则,首先要定义 RateLimitFilter ...
- 字节跳动基于ClickHouse优化实践之“多表关联查询”
更多技术交流.求职机会.试用福利,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 相信大家都对大名鼎鼎的ClickHouse有一定的了解了,它强大的数据分析性能让人印象深刻.但在字节大量 ...
- StarRocks 运维工具 StarGo
注:本文主要内容均来源 StarRocks 官网 https://docs.starrocks.com/zh-cn/main/administration/stargo StarGo 是一个用于管理多 ...