Kabar Baik, Semuanya!
Kernel Linux 5.0 sudah ada di sini dan muncul dalam distribusi eksperimental seperti Arch, openSUSE Tumbleweed, Fedora.

Dan jika Anda melihat distribusi RC dari Ubuntu Disko Dingo dan Red Hat 8, maka menjadi jelas: segera kernel 5.0 juga akan ditransfer dari desktop fan ke server yang serius.
Seseorang akan berkata - jadi apa. Rilis berikutnya, tidak ada yang istimewa. Jadi, Linus Torvalds sendiri berkata:
Saya ingin menunjukkan (lagi) bahwa kita tidak melakukan rilis berbasis fitur, dan bahwa "5.0" tidak berarti apa-apa lebih dari itu angka 4.x mulai menjadi cukup besar sehingga saya kehabisan jari dan jari kaki.
( Sekali lagi saya ulangi - rilis kami tidak terikat dengan fitur tertentu, sehingga jumlah versi baru 5.0 hanya berarti bahwa untuk penomoran versi 4.x saya sudah tidak punya cukup jari tangan dan kaki )
Namun, modul untuk floppy disk (siapa yang tidak tahu - ini adalah disk seukuran baju saku dada, dengan kapasitas 1,44 MB) - dikoreksi ...
Dan inilah alasannya:
Ini semua tentang multi-queue block layer (blk-mq). Ada banyak artikel pengantar tentang dia di Internet, jadi mari kita langsung ke intinya. Transisi ke blk-mq dimulai sejak lama dan perlahan-lahan bergerak maju. Scsi multi-antrian (parameter kernel scsi_mod.use_blk_mq) muncul, penjadwal baru mq-tenggat waktu, bfq, dan seterusnya muncul ...
[root@fedora-29 sblkdev]# cat /sys/block/sda/queue/scheduler [mq-deadline] none
Ngomong-ngomong, apa milikmu?
Jumlah pengandar perangkat blok yang bekerja dengan cara lama berkurang. Dan pada 5.0, fungsi blk_init_queue () dihapus sebagai tidak perlu. Dan sekarang kode lama
lwn.net/Articles/58720 dari tahun 2003 tidak hanya tidak akan berhasil, tetapi juga kehilangan relevansi. Selain itu, distribusi baru, yang sedang dipersiapkan untuk rilis tahun ini, menggunakan lapisan blok multi-antrian dalam konfigurasi default. Sebagai contoh, pada Manjaro ke-18, kernel, meskipun versi 4.19, secara default blk-mq.
Oleh karena itu, kita dapat mengasumsikan bahwa transisi ke blk-mq di kernel 5.0 telah selesai. Dan bagi saya ini adalah peristiwa penting yang akan membutuhkan penulisan ulang kode dan pengujian tambahan. Yang dengan sendirinya menjanjikan munculnya bug besar dan kecil, serta beberapa server macet (Kita harus, Fedya, aku harus! (C)).
By the way, jika seseorang berpikir bahwa untuk rhel8 titik kritis ini tidak datang, karena kernel "flash" oleh versi 4.18 di sana, maka Anda salah. Dalam RC baru di rhel8, produk baru dari 5.0 sudah dimigrasikan, dan fungsi blk_init_queue () juga terpotong (mungkin ketika menyeret check-in lain dari github.com/torvalds/linux ke sumbernya).
Secara umum, versi "beku" dari kernel untuk distributor Linux seperti SUSE dan Red Hat telah lama menjadi konsep pemasaran. Sistem melaporkan bahwa versi tersebut, misalnya, adalah 4.4, dan sebenarnya fungsionalitasnya berasal dari vanilla 4.8 baru. Pada saat yang sama, sebuah prasasti memamerkan di situs web resmi seperti: "Dalam distribusi baru, kami telah menyimpan kernel 4.4 yang stabil untuk Anda."
Tapi kami terganggu ...
Jadi disini. Kami membutuhkan driver perangkat blok sederhana baru untuk membuatnya lebih jelas bagaimana ini bekerja.
Jadi, sumbernya di
github.com/CodeImp/sblkdev . Saya mengusulkan untuk berdiskusi, membuat permintaan tarik, memulai masalah - saya akan memperbaikinya. QA belum diuji.
Nanti dalam artikel saya akan mencoba menjelaskan apa sebabnya. Karena itu, ada banyak kode.
Saya langsung minta maaf karena gaya pengkodean kernel Linux tidak sepenuhnya dihormati, dan ya - saya tidak suka goto.
Jadi, mari kita mulai dari titik masuk.
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);
Jelas, ketika modul dimuat, fungsi sblkdev_init () diluncurkan, ketika sblkdev_exit () diturunkan.
Fungsi register_blkdev () mendaftarkan perangkat blok. Ia dialokasikan sejumlah besar. unregister_blkdev () - membebaskan nomor ini.
Struktur kunci dari modul kami adalah sblkdev_device_t.
Ini berisi semua informasi tentang perangkat yang diperlukan untuk modul kernel, khususnya: kapasitas perangkat blok, data itu sendiri (ini sederhana), petunjuk ke disk dan antrian.
Semua inisialisasi perangkat blok dilakukan dalam fungsi 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
Kami mengalokasikan memori untuk struktur, mengalokasikan buffer untuk menyimpan data. Tidak ada yang istimewa di sini.
Selanjutnya, kami menginisialisasi antrian pemrosesan permintaan dengan salah satu fungsi blk_mq_init_sq_queue (), atau dua sekaligus: blk_mq_alloc_tag_set () + blk_mq_init_queue ().
Ngomong-ngomong, jika Anda melihat sumber dari fungsi blk_mq_init_sq_queue (), Anda akan melihat bahwa ini hanyalah pembungkus di atas fungsi blk_mq_alloc_tag_set () dan blk_mq_init_queue (), yang muncul di kernel 4.20. Selain itu, ia menyembunyikan banyak parameter antrian, tetapi terlihat lebih sederhana. Anda harus memilih opsi mana yang lebih baik, tetapi saya lebih suka yang lebih eksplisit.
Kunci dalam kode ini adalah variabel global _mq_ops.
static struct blk_mq_ops _mq_ops = { .queue_rq = queue_rq, };
Di sinilah fungsi yang menyediakan pemrosesan permintaan berada, tetapi lebih banyak tentangnya nanti. Hal utama adalah bahwa kami telah menetapkan titik masuk ke penangan permintaan.
Sekarang kita telah membuat antrian, kita dapat membuat instance dari disk.
Tidak ada perubahan besar. Disk dialokasikan, parameter ditetapkan, dan disk ditambahkan ke sistem. Saya ingin menjelaskan tentang parameter disk-> flags. Ini memungkinkan Anda untuk memberi tahu sistem bahwa disk dapat dilepas, atau, misalnya, bahwa itu tidak mengandung partisi dan Anda tidak perlu mencarinya di sana.
Ada struktur _fops untuk manajemen disk.
static const struct block_device_operations _fops = { .owner = THIS_MODULE, .open = _open, .release = _release, .ioctl = _ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = _compat_ioctl, #endif };
Poin entri _open dan _release bagi kami untuk modul perangkat blok sederhana belum terlalu menarik. Selain penghitung kenaikan dan penurunan atom, tidak ada apa pun di sana. Saya juga meninggalkan compat_ioctl tanpa implementasi, karena versi sistem dengan kernel 64-bit dan lingkungan ruang pengguna 32-bit sepertinya tidak menjanjikan bagi saya.
Tetapi _ioctl memungkinkan Anda memproses permintaan sistem untuk drive ini. Ketika disk muncul, sistem mencoba untuk mempelajari lebih lanjut tentang itu. Anda dapat menjawab beberapa pertanyaan sesuai keinginan Anda (misalnya, berpura-pura menjadi CD baru), tetapi aturan umumnya adalah ini: jika Anda tidak ingin menjawab pertanyaan yang tidak menarik bagi Anda, cukup kembalikan kode kesalahan -ENOTTY. Omong-omong, jika perlu, di sini Anda dapat menambahkan penangan permintaan Anda tentang drive khusus ini.
Jadi, kami menambahkan perangkat - kami harus mengurus pelepasan sumber daya. Karat tidak di
sini untukmu.
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"); } }
Pada prinsipnya, semuanya jelas: kami menghapus objek disk dari sistem dan membebaskan antrian, setelah itu kami juga membebaskan buffer kami (area data).
Dan sekarang yang paling penting adalah pemrosesan query dalam fungsi 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);
Pertama, pertimbangkan parameternya. Yang pertama adalah struct blk_mq_hw_ctx * hctx - keadaan antrian perangkat keras. Dalam kasus kami, kami melakukannya tanpa antrian perangkat keras, jadi tidak digunakan.
Parameter kedua adalah const struct blk_mq_queue_data * bd - parameter dengan struktur yang sangat ringkas, yang saya tidak takut untuk memberikan perhatian Anda secara keseluruhan:
struct blk_mq_queue_data { struct request *rq; bool last; };
Ternyata pada dasarnya ini semua permintaan yang sama yang datang kepada kita dari waktu yang
elixir.bootlin.com kronik tidak lagi ingat. Jadi kami menerima permintaan dan mulai memprosesnya, yang kami beri tahu kernel dengan menelepon blk_mq_start_request (). Setelah menyelesaikan pemrosesan permintaan, kami akan menginformasikan kernel tentang hal ini dengan memanggil fungsi blk_mq_end_request ().
Berikut ini catatan kecil: fungsi blk_mq_end_request () pada dasarnya adalah pembungkus panggilan ke blk_update_request () + __blk_mq_end_request (). Saat menggunakan fungsi blk_mq_end_request (), Anda tidak bisa menentukan berapa banyak byte yang sebenarnya diproses. Percaya bahwa semuanya sudah diproses.
Opsi alternatif memiliki fitur lain: fungsi blk_update_request diekspor hanya untuk modul khusus GPL. Yaitu, jika Anda ingin membuat modul kernel berpemilik (biarkan PM menyelamatkan Anda dari jalur sulit ini), Anda tidak dapat menggunakan blk_update_request (). Jadi pilihan ada di tangan Anda.
Secara langsung menggeser byte dari permintaan ke buffer dan sebaliknya saya memasukkan fungsi 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))
Tidak ada yang baru: rq_for_each_segment iterates atas semua bio, dan mereka semua memiliki struktur bio_vec, memungkinkan kita untuk sampai ke halaman dengan data permintaan.
Apa kesan Anda? Segalanya tampak sederhana? Pemrosesan permintaan secara umum hanya menyalin data antara halaman permintaan dan buffer internal. Cukup layak untuk driver perangkat blok sederhana, bukan?
Tapi ada masalah:
Ini bukan untuk penggunaan nyata!Inti dari masalah adalah bahwa fungsi pemrosesan permintaan queue_rq () dipanggil dalam loop yang memproses permintaan dari daftar. Saya tidak tahu kunci mana untuk daftar ini yang digunakan di sana, Putar atau RCU (Saya tidak ingin berbohong - siapa tahu, perbaiki saya), tetapi ketika Anda mencoba menggunakan, misalnya, mutex dalam fungsi pemrosesan permintaan, kernel debugging bersumpah dan memperingatkan: tertidur ini tidak mungkin. Artinya, menggunakan alat sinkronisasi konvensional atau memori bersebelahan virtual - yang dialokasikan menggunakan vmalloc dan dapat jatuh ke swap dengan semua yang disiratkannya - tidak mungkin, karena proses tidak dapat masuk ke keadaan siaga.
Oleh karena itu, baik Spin atau RCU mengunci dan buffer dalam bentuk array halaman, atau daftar, atau pohon, seperti yang diterapkan di .. \ linux \ drivers \ block \ brd.c, atau menunda pemrosesan di utas lain, seperti yang diterapkan dalam .. \ linux \ drivers \ block \ loop.c.
Saya pikir tidak perlu menjelaskan cara merakit modul, cara memuatnya ke dalam sistem dan cara membongkar. Tidak ada produk baru di bagian depan ini, dan terima kasih untuk itu :) Jadi jika seseorang ingin mencobanya, saya pasti akan mengetahuinya.
Jangan langsung melakukannya di laptop favorit Anda! Naikkan virtualochka atau setidaknya buat cadangan di atas bola.
Omong-omong, Veeam Backup untuk Linux 3.0.1.1046 sudah tersedia. Hanya saja, jangan mencoba menjalankan VAL 3.0.1.1046 pada kernel 5.0 atau yang lebih baru. veeamsnap tidak akan berkumpul. Dan beberapa inovasi multi-antrian masih dalam tahap pengujian.