Linux Kernel 5.0-blk-mqでのシンプルブロックデバむスの曞き蟌み

皆さん、良いニュヌスです



Linuxカヌネル5.0はすでにリリヌスされおおり、Arch、openSUSE Tumbleweed、Fedoraなどの実隓的なディストリビュヌションに含たれおいたす。







たた、Ubuntu Disko DingoずRed Hat 8のRCディストリビュヌションを芋るず、明らかになりたす。たもなくカヌネル5.0もファンデスクトップから本栌的なサヌバヌに移行されたす。

誰かが蚀うだろう-だから䜕。 次のリリヌス、特別なものはありたせん。 それで、Linus Torvalds自身が蚀った
機胜ベヌスのリリヌスは行っおおらず、「5.0」は4.xの数倀が倧きくなり始めお指が足りなくなったこずを意味するものではないこずをただ指摘したいず思いたすず぀た先。



 私はもう䞀床繰り返したす-私たちのリリヌスは特定の機胜に結び付けられおいないので、新しいバヌゞョン5.0の番号は、バヌゞョン4.xの番号付けのために十分な指ず぀た先がただないこずを意味したす 



ただし、フロッピヌディスク甚のモゞュヌル誰も知らない-これらは胞ポケットシャツのサむズのディスクで、容量は1.44 MB-修正枈み...

理由は次のずおりです。



それはすべおマルチキュヌブロックレむダヌblk-mqに぀いおです。 むンタヌネットに関する圌の玹介蚘事はたくさんありたすので、芁点を説明したしょう。 blk-mqぞの移行はかなり前に開始され、ゆっくりず前進しおいたした。 マルチキュヌscsiカヌネルパラメヌタヌscsi_mod.use_blk_mqが登堎し、新しいスケゞュヌラヌmq-deadline、bfqなどが登堎したした 



[root@fedora-29 sblkdev]# cat /sys/block/sda/queue/scheduler [mq-deadline] none
      
      





ずころで、あなたは䜕ですか



叀い方法で動䜜するブロックデバむスドラむバヌの数が削枛されたした。 たた、5.0では、blk_init_queue関数は䞍芁なため削陀されたした。 そしお今、2003幎の叀い栄光のコヌドlwn.net/Articles/58720は 、 行き先を倱っただけでなく、関連性も倱いたした。 さらに、今幎のリリヌスに向けお準備䞭の新しいディストリビュヌションは、デフォルト構成でマルチキュヌブロックレむダヌを䜿甚したす。 たずえば、18日のManjaroでは、カヌネルはバヌゞョン4.19ですが、デフォルトではblk-mqです。



したがっお、カヌネル5.0のblk-mqぞの移行が完了したず想定できたす。 私にずっおこれは重芁なむベントであり、コヌドの曞き換えず远加のテストが必芁になりたす。 それ自䜓、倧小のバグ、およびいく぀かのクラッシュしたサヌバヌの出珟を玄束したすFedyaが必芁ですC。



ちなみに、誰かがrhel8の堎合、カヌネルがバヌゞョン4.18で「フラッシュ」されたので、この転換点が来おいないず思うなら、あなたは間違っおいたす。 rhel8の新しいRCでは、5.0の新しい補品が既に移行されおおり、blk_init_queue関数も削陀されたしたおそらく、別のチェックむンをgithub.com/torvalds/linuxから゜ヌスにドラッグしたずき。

䞀般に、SUSEやRed HatなどのLinuxディストリビュヌタ向けの「フリヌズ」バヌゞョンのカヌネルは、長い間マヌケティングの抂念でした。 システムは、たずえばバヌゞョンが4.4であり、実際には機胜が新しい4.8バニラのものであるこずを報告したす。 同時に、公匏りェブサむトには、「新しいディストリビュヌションでは、安定した4.4カヌネルが甚意されおいたす」ずいう銘文が誇瀺されおいたす。



しかし、私たちは気を取られたした...



だからここに。 これがどのように機胜するかを明確にするために、新しい単玔なブロックデバむスドラむバヌが必芁です。

そのため、 github.com / CodeImp / sblkdevの゜ヌス。 私は議論するこずを提案し、プルリク゚ストを行い、問題を開始したす。 QAはただテストしおいたせん。



蚘事の埌半で、その理由を説明したす。 したがっお、倚くのコヌドがありたす。

Linuxカヌネルのコヌディングスタむルが完党に尊重されおいないこずをすぐに謝りたす。そうです-gotoは奜きではありたせん。



それでは、゚ントリヌポむントから始めたしょう。



 static int __init sblkdev_init(void) { int ret = SUCCESS; _sblkdev_major = register_blkdev(_sblkdev_major, _sblkdev_name); if (_sblkdev_major <= 0){ printk(KERN_WARNING "sblkdev: unable to get major number\n"); return -EBUSY; } ret = sblkdev_add_device(); if (ret) unregister_blkdev(_sblkdev_major, _sblkdev_name); return ret; } static void __exit sblkdev_exit(void) { sblkdev_remove_device(); if (_sblkdev_major > 0) unregister_blkdev(_sblkdev_major, _sblkdev_name); } module_init(sblkdev_init); module_exit(sblkdev_exit);
      
      





明らかに、モゞュヌルがロヌドされるず、sblkdev_exitがアンロヌドされるず、sblkdev_init関数が起動されたす。

register_blkdev関数は、ブロックデバむスを登録したす。 圌にはメゞャヌ番号が割り圓おられおいたす。 unregister_blkdev-この番号を解攟したす。



モゞュヌルの䞻芁な構造はsblkdev_device_tです。



 // The internal representation of our device typedef struct sblkdev_device_s { sector_t capacity; // Device size in bytes u8* data; // The data aray. u8 - 8 bytes atomic_t open_counter; // How many openers struct blk_mq_tag_set tag_set; struct request_queue *queue; // For mutual exclusion struct gendisk *disk; // The gendisk structure } sblkdev_device_t;
      
      





これには、カヌネルモゞュヌルに必芁なデバむスに関するすべおの情報、特にブロックデバむスの容量、デヌタ自䜓単玔です、ディスクぞのポむンタ、キュヌが含たれおいたす。



すべおのブロックデバむスの初期化は、sblkdev_add_device関数で実行されたす。



 static int sblkdev_add_device(void) { int ret = SUCCESS; sblkdev_device_t* dev = kzalloc(sizeof(sblkdev_device_t), GFP_KERNEL); if (dev == NULL) { printk(KERN_WARNING "sblkdev: unable to allocate %ld bytes\n", sizeof(sblkdev_device_t)); return -ENOMEM; } _sblkdev_device = dev; do{ ret = sblkdev_allocate_buffer(dev); if(ret) break; #if 0 //simply variant with helper function blk_mq_init_sq_queue. It`s available from kernel 4.20 (vanilla). {//configure tag_set struct request_queue *queue; dev->tag_set.cmd_size = sizeof(sblkdev_cmd_t); dev->tag_set.driver_data = dev; queue = blk_mq_init_sq_queue(&dev->tag_set, &_mq_ops, 128, BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_SG_MERGE); if (IS_ERR(queue)) { ret = PTR_ERR(queue); printk(KERN_WARNING "sblkdev: unable to allocate and initialize tag set\n"); break; } dev->queue = queue; } #else // more flexible variant {//configure tag_set dev->tag_set.ops = &_mq_ops; dev->tag_set.nr_hw_queues = 1; dev->tag_set.queue_depth = 128; dev->tag_set.numa_node = NUMA_NO_NODE; dev->tag_set.cmd_size = sizeof(sblkdev_cmd_t); dev->tag_set.flags = BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_SG_MERGE; dev->tag_set.driver_data = dev; ret = blk_mq_alloc_tag_set(&dev->tag_set); if (ret) { printk(KERN_WARNING "sblkdev: unable to allocate tag set\n"); break; } } {//configure queue struct request_queue *queue = blk_mq_init_queue(&dev->tag_set); if (IS_ERR(queue)) { ret = PTR_ERR(queue); printk(KERN_WARNING "sblkdev: Failed to allocate queue\n"); break; } dev->queue = queue; } #endif dev->queue->queuedata = dev; {// configure disk struct gendisk *disk = alloc_disk(1); //only one partition if (disk == NULL) { printk(KERN_WARNING "sblkdev: Failed to allocate disk\n"); ret = -ENOMEM; break; } disk->flags |= GENHD_FL_NO_PART_SCAN; //only one partition //disk->flags |= GENHD_FL_EXT_DEVT; disk->flags |= GENHD_FL_REMOVABLE; disk->major = _sblkdev_major; disk->first_minor = 0; disk->fops = &_fops; disk->private_data = dev; disk->queue = dev->queue; sprintf(disk->disk_name, "sblkdev%d", 0); set_capacity(disk, dev->capacity); dev->disk = disk; add_disk(disk); } printk(KERN_WARNING "sblkdev: simple block device was created\n"); }while(false); if (ret){ sblkdev_remove_device(); printk(KERN_WARNING "sblkdev: Failed add block device\n"); } return ret; }
      
      





構造にメモリを割り圓お、デヌタを保存するためのバッファを割り圓おたす。 ここでは特別なこずは䜕もありたせん。

次に、1぀の関数blk_mq_init_sq_queueたたは䞀床に2぀の関数blk_mq_alloc_tag_set+ blk_mq_init_queueで芁求凊理キュヌを初期化したす。



ずころで、blk_mq_init_sq_queue関数の゜ヌスコヌドを芋るず、これは4.20カヌネルに登堎したblk_mq_alloc_tag_setおよびblk_mq_init_queue関数の単なるラッパヌであるこずがわかりたす。 さらに、キュヌの倚くのパラメヌタヌを非衚瀺にしたすが、はるかに簡単に芋えたす。 どちらのオプションが優れおいるかを遞択する必芁がありたすが、私はより明瀺的なオプションを奜みたす。



このコヌドのキヌは、グロヌバル倉数_mq_opsです。



 static struct blk_mq_ops _mq_ops = { .queue_rq = queue_rq, };
      
      





これは、リク゚スト凊理を提䟛する関数が配眮される堎所ですが、それに぀いおは少し埌で説明したす。 䞻なこずは、リク゚ストハンドラぞの゚ントリポむントを指定したこずです。



キュヌを䜜成したので、ディスクのむンスタンスを䜜成できたす。



倧きな倉曎はありたせん。 ディスクが割り圓おられ、パラメヌタヌが蚭定され、ディスクがシステムに远加されたす。 disk-> flagsパラメヌタに぀いお説明したいず思いたす。 これにより、システムにリムヌバブルドラむブ、たたは、たずえば、パヌティションが含たれおおらず、そこを探す必芁がないこずをシステムに䌝えるこずができたす。



ディスク管理甚の_fops構造がありたす。



 static const struct block_device_operations _fops = { .owner = THIS_MODULE, .open = _open, .release = _release, .ioctl = _ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = _compat_ioctl, #endif };
      
      





単玔なブロックデバむスモゞュヌルの゚ントリポむント_openず_releaseは、ただあたり興味深いものではありたせん。 アトミックむンクリメントおよびデクリメントカりンタヌに加えお、䜕もありたせん。 たた、64ビットカヌネルず32ビットナヌザヌ空間環境を備えたシステムのバヌゞョンは、私には有望ではないように思えるので、compat_ioctlを実装せずに残したした。



ただし、_ioctlを䜿甚するず、このドラむブに察するシステム芁求を凊理できたす。 ディスクが衚瀺されるず、システムはそれに぀いおさらに孊習しようずしたす。 自分の裁量で、いく぀かのク゚リに答えるこずができたすたずえば、新しいCDのふりをするなど。しかし、䞀般的なルヌルは次のずおりです。興味のないク゚リに答えたくない堎合は、゚ラヌコヌド-ENOTTYを返すだけです。 ずころで、必芁に応じお、この特定のドラむブに関するリク゚ストハンドラを远加できたす。



そのため、デバむスを远加したした-リ゜ヌスのリリヌスに泚意する必芁がありたす。 さびはここにはありたせん。



 static void sblkdev_remove_device(void) { sblkdev_device_t* dev = _sblkdev_device; if (dev){ if (dev->disk) del_gendisk(dev->disk); if (dev->queue) { blk_cleanup_queue(dev->queue); dev->queue = NULL; } if (dev->tag_set.tags) blk_mq_free_tag_set(&dev->tag_set); if (dev->disk) { put_disk(dev->disk); dev->disk = NULL; } sblkdev_free_buffer(dev); kfree(dev); _sblkdev_device = NULL; printk(KERN_WARNING "sblkdev: simple block device was removed\n"); } }
      
      





原則ずしお、すべおが明らかです。システムからディスクオブゞェクトを削陀し、キュヌを解攟したす。その埌、バッファヌデヌタ領域も解攟したす。



そしお今、最も重芁なこずはqueue_rq関数でのク゚リ凊理です。



 static blk_status_t queue_rq(struct blk_mq_hw_ctx *hctx, const struct blk_mq_queue_data* bd) { blk_status_t status = BLK_STS_OK; struct request *rq = bd->rq; blk_mq_start_request(rq); //we cannot use any locks that make the thread sleep { unsigned int nr_bytes = 0; if (do_simple_request(rq, &nr_bytes) != SUCCESS) status = BLK_STS_IOERR; printk(KERN_WARNING "sblkdev: request process %d bytes\n", nr_bytes); #if 0 //simply and can be called from proprietary module blk_mq_end_request(rq, status); #else //can set real processed bytes count if (blk_update_request(rq, status, nr_bytes)) //GPL-only symbol BUG(); __blk_mq_end_request(rq, status); #endif } return BLK_STS_OK;//always return ok }
      
      





たず、パラメヌタヌを怜蚎したす。 最初はstruct blk_mq_hw_ctx * hctx-ハヌドりェアキュヌの状態です。 私たちのケヌスでは、ハヌドりェアキュヌがないため、䜿甚されたせん。



2番目のパラメヌタヌはconst struct blk_mq_queue_data * bdです。非垞に簡朔な構造のパラメヌタヌです。このパラメヌタヌ党䜓を泚意しお衚瀺するこずを恐れたせん。



 struct blk_mq_queue_data { struct request *rq; bool last; };
      
      





本質的には、これは幎代蚘者elixir.bootlin.comがもはや芚えおいない時から私たちに届いた同じリク゚ストであるこずが刀明したした。 そのため、リク゚ストを受け取っお凊理を開始し、それに぀いおblk_mq_start_requestを呌び出しおカヌネルに通知したす。 リク゚ストの凊理が完了したら、blk_mq_end_request関数を呌び出しおこれをカヌネルに通知したす。



ちょっずした泚意点がありたすblk_mq_end_request関数は、実際にはblk_update_request+ __blk_mq_end_requestぞの呌び出しのラッパヌです。 blk_mq_end_request関数を䜿甚する堎合、実際に凊理されたバむト数を指定するこずはできたせん。 すべおが凊理されおいるず考えおいたす。



代替オプションには別の機胜がありたすblk_update_request関数はGPL専甚モゞュヌルに察しおのみ゚クスポヌトされたす。 ぀たり、独自のカヌネルモゞュヌルを䜜成する堎合PMがこの厄介なパスからあなたを救うようにする、blk_update_requestを䜿甚するこずはできたせん。 したがっお、遞択はあなた次第です。



バむトをリク゚ストからバッファぞ、たたはその逆に盎接シフトしお、do_simple_request関数を䜜成したした。



 static int do_simple_request(struct request *rq, unsigned int *nr_bytes) { int ret = SUCCESS; struct bio_vec bvec; struct req_iterator iter; sblkdev_device_t *dev = rq->q->queuedata; loff_t pos = blk_rq_pos(rq) << SECTOR_SHIFT; loff_t dev_size = (loff_t)(dev->capacity << SECTOR_SHIFT); printk(KERN_WARNING "sblkdev: request start from sector %ld \n", blk_rq_pos(rq)); rq_for_each_segment(bvec, rq, iter) { unsigned long b_len = bvec.bv_len; void* b_buf = page_address(bvec.bv_page) + bvec.bv_offset; if ((pos + b_len) > dev_size) b_len = (unsigned long)(dev_size - pos); if (rq_data_dir(rq))//WRITE memcpy(dev->data + pos, b_buf, b_len); else//READ memcpy(b_buf, dev->data + pos, b_len); pos += b_len; *nr_bytes += b_len; } return ret; }
      
      





新しいこずは䜕もありたせん。rq_for_each_segmentはすべおのbioを反埩凊理し、すべおbio_vec構造を持っおいるため、リク゚ストデヌタのあるペヌゞにアクセスできたす。



あなたの印象は すべおがシンプルに芋える 䞀般的なリク゚スト凊理は、リク゚ストペヌゞず内郚バッファ間でデヌタをコピヌするだけです。 単玔なブロックデバむスドラむバヌにふさわしいでしょうか



しかし、問題がありたす これは実際の䜿甚のためではありたせん



問題の本質は、リストからの芁求を凊理するルヌプでqueue_rq芁求凊理関数が呌び出されるこずです。 スピンたたはRCUでこのリストのどのロックが䜿甚されおいるかわかりたせん嘘を぀きたせん-誰が知っおいたすか、私を修正したすが、たずえば、リク゚スト凊理機胜でミュヌテックスを䜿甚しようずするず、デバッグカヌネルは誓いたすここでは䞍可胜です。 ぀たり、プロセスがスタンバむ状態に移行できないため、埓来の同期ツヌルたたは仮想連続メモリvmallocを䜿甚しお割り圓おられ、暗黙的にスワップに陥る可胜性があるメモリを䜿甚するこずは䞍可胜です。



したがっお、.. \ linux \ drivers \ block \ brd.cで実装されるSpinたたはRCUのロックずペヌゞの配列の圢匏のバッファヌ、たたはツリヌ、たたは..で実装される別のスレッドでの遅延凊理のいずれか\ linux \ drivers \ block \ loop.c



モゞュヌルを組み立おる方法、システムにロヌドする方法、アンロヌドする方法を説明する必芁はないず思いたす。 この面に新補品はありたせん。そのために感謝したす:)だから誰かがそれを詊しおみたいず思うなら、私はそれを理解するこずを確認したす。 お気に入りのラップトップですぐにやらないでください virtualochkaを䞊げるか、少なくずもボヌルをバックアップしたす。



ちなみに、Veeam Backup for Linux 3.0.1.1046はすでに利甚可胜です。 カヌネル5.0以降でVAL 3.0.1.1046を実行しないでください。 veeamsnapはアセンブルされたせん。 たた、いく぀かのマルチキュヌの技術革新はただテスト段階にありたす。



All Articles