Cara Menulis Modul Kernel Linux Yang Simple - CRUDPRO

Cara Menulis Modul Kernel Linux Yang Simple

Cara Menulis Modul Kernel Linux Yang Simple

Meraih Golden Ring-0

Linux menyediakan API yang kuat dan luas untuk program, tapi terkadang itu tidaklah cukup. Berhubungan dengan perangkat keras atau melakukan operasi yang membutuhkan akses ke info spesial dalam sistem membutuhkan modul kernel.

Modul kernel Linux ialah sisi dari code biner terkompilasi yang ditempatkan langsung ke kernel Linux, berjalan pada ring 0, ring eksekusi paling rendah dan paling tidak terlindungi dalam processor x86-64. Code di sini jalan sepenuhnya tidak dicentang tapi bekerja dengan kecepatan hebat dan mempunyai akses ke semuanya yang berada di sistem.

Tidak untuk Mere Mortals

Menulis modul kernel Linux bukan untuk menjadi faint of heart. Dengan mengganti kernel, Anda beresiko kehilangan data dan kerusakan sistem. Code kernel tidak mempunyai jaringan pengaman biasa yang dinikmati program Linux biasa. Bila Anda memiliki kesalahan, itu akan mengamankan semua sistem.

Worse, permasalahan Anda mungkin tidak segera terlihat. Modul Anda langsung terkunci sesudah memuat kemungkinan sebagai scenario kasus terbaik untuk ketidakberhasilan. Saat Anda menambah semakin banyak code ke modul, Anda beresiko mengakibatkan runaway loop dan kebocoran memory. Bila Anda tidak hati-hati, ini terus semakin bertambah bersamaan mesin Anda terus berjalan. Pada akhirnya struktur memory penting serta buffer bisa ditimpa.

Paradigma peningkatan program tradisional mayoritas bisa dibuang. Selain memuat dan membongkar modul Anda, Anda akan menulis code yang memberi respon kejadian sistem dibanding beroperasi dalam pola berurut. Dengan peningkatan kernel, Anda sedang menulis API, bukan program tersebut.

Anda juga tidak mempunyai akses ke perpustakaan standard. Sementara kernel sediakan fungsi-fungsi seperti printk (yang berperan sebagai alternatif printf) dan kmalloc (yang bekerja secara serupa dengan malloc), Anda mayoritas didiamkan menggunakan perangkat Anda sendiri. Disamping itu, saat modul Anda dibongkar, Anda bertanggungjawab untuk membersihkan sendiri seutuhnya. Tidak ada pengumpulan trash.

Persyaratan

Sebelum mengawali, kita perlu pastikan jika kita mempunyai alat yang pas untuk tugas itu. Yang paling penting, Anda membutuhkan mesin Linux. Saya mengerti itu benar-benar mengagetkan! Walau distribusi Linux apa pun bisa dipakai, saya memakai Ubuntu 16.04 LTS dalam contoh ini, jadi jika Anda memakai distribusi yang lain, Anda kemungkinan perlu sedikit sesuaikan perintah instalasi.

Kedua, Anda membutuhkan mesin fisik terpisah atau mesin virtual. Saya lebih suka lakukan tugas saya di mesin virtual, tapi ini seutuhnya terserah Anda. Saya tidak merekomendasikan memakai mesin khusus Anda karena kehilangan data bisa terjadi saat Anda melakukan kesalahan. Saya katakan kapan, bukan bila, karena Anda pasti mengunci mesin Anda minimal beberapa kali sepanjang proses itu. Peralihan code terkini Anda mungkin berada di buffer note saat kernel worried, jadi mungkin file sumber Anda hancur. Pengetesan di mesin virtual menghilangkan resiko ini.

Dan paling akhir, Anda harus ketahui minimal beberapa C. Runtime C++ terlampau besar untuk kernel, jadi menulis C bare metal sangat penting. Untuk hubungan dengan perangkat keras, mengetahui beberapa perakitan kemungkinan dapat menolong.

Menginstal environment Pengembangan

Di Ubuntu, kita perlu menjalankan:

apt-get install build-essential linux-headers-`uname -r`

Ini akan menginstal alat pengembangan penting dan header kernel yang dibutuhkan untuk contoh ini.

Contoh di bawah menganggap Anda menjalankan sebagai pengguna biasa dan bukan root, tapi Anda mempunyai hak spesial sudo. Sudo penting untuk memuat modul kernel, tapi kami ingin bekerja di luar root bila memungkinkannya.

Mulai

Mari mulai menulis beberapa code. Silahkan siapkan environment kita:

mkdir ~/src/lkm_example
cd ~/src/lkm_example

Jalankan editor favorit Anda (dalam kasus saya, ini adalah vim) dan buat file lkm_example.c dengan konten berikut:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“Robert W. Oliver II”);
MODULE_DESCRIPTION(“A simple example Linux module.”);
MODULE_VERSION(“0.01”);static int __init lkm_example_init(void) {
 printk(KERN_INFO “Hello, World!\n”);
 return 0;
}static void __exit lkm_example_exit(void) {
 printk(KERN_INFO “Goodbye, World!\n”);
}module_init(lkm_example_init);
module_exit(lkm_example_exit);

Saat ini setelah kita membuat modul yang paling simpel, mari kita contohkan beberapa bagian penting secara mendalam:

The "includes" meliputi file header yang dibutuhkan yang dibutuhkan untuk peningkatan kernel Linux.

MODULE_LICENSE bisa ditata ke berbagai nilai tergantung pada lisensi modul. Untuk melihat urutan lengkap, jalankan:
grep "MODULE_LICENSE" -B 27 /usr/src/linux-headers-`uname -r`/include/linux/module.h

Kami mendeskripsikan fungsi init (loading) dan exit (unloading) sebagai statis dan mengembalikan int.

Lihat penggunaan printk bukan printf. Disamping itu, printk tidak share parameter yang serupa dengan printf. Misalkan, KERN_INFO, yang merupakan bendera untuk menyatakan prioritas logging apa yang perlu ditetapkan untuk baris ini, didefinisikan tanpa koma. Kernel memilah ini dalam fungsi printk untuk menghemat memory tumpukan.

Diakhir file, kami memanggil module_init dan module_exit untuk memberitahu kernel mana fungsi atau fungsi unloading and loading. Ini memberikan kita kebebasan untuk memberikan nama fungsi apa pun yang kita sukai.

Kami tidak bisa mengkompilasi file ini. Kami membutuhkan Makefile. Contoh dasar ini akan berfungsi untuk sekarang ini. Lihat jika make sangat pilih-pilih tentang spasi dan tab, jadi pastikan Anda memakai tab, bukan spasi bila perlu.

obj-m += lkm_example.oall:
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:
 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Jika kami menjalankan "make", itu harus mengkompilasi modul Anda dengan sukses. File yang dihasilkan adalah “lkm_example.ko”. Jika Anda menerima kesalahan apa pun, periksa apakah tanda kutip Anda di file sumber contoh sudah benar dan tidak disisipkan secara tidak sengaja sebagai karakter UTF-8.

Sekarang kita dapat memasukkan modul untuk mengujinya. Untuk melakukan ini, jalankan:

sudo insmod lkm_example.ko

Bila semua berjalan baik, Anda tidak melihat apapun. Fungsi printk tidak keluar ke konsol tetapi log kernel. Untuk melihatnya, kita harus jalankan:

sudo dmesg

Anda akan melihat pesan "Hallo, World!" baris dengan diawali stempel waktu. Ini berarti modul kernel kami dimuat dan sukses dicetak ke log kernel. Kami juga bisa mengecek untuk melihat apa modul saat ini masih dimuat:

lsmod | grep “lkm_example”

Untuk menghapus modul, jalankan:

sudo rmmod lkm_example

Jika Anda menjalankan dmesg lagi, Anda akan melihat “Goodbye, World!” dalam log. Anda juga dapat menggunakan lsmod lagi untuk mengonfirmasi bahwa itu telah dibongkar.

Seperti yang Anda lihat, alur kerja pengujian ini agak membosankan, jadi untuk mengotomatiskannya, kita dapat menambahkan:

test:
 sudo dmesg -C
 sudo insmod lkm_example.ko
 sudo rmmod lkm_example.ko
 dmesg

di akhir Makefile kami dan sekarang jalankan:

make test

untuk mengetes modul kami dan melihat keluaran dari log kernel tanpa harus jalankan perintah terpisah.

Saat ini kami mempunyai modul kernel yang berfungsi penuh, tetapi seutuhnya sepele!

A Little More Interesting

Mari pelajari sedikit lebih dalam. Walaupun modul kernel bisa menuntaskan semua tipe pekerjaan, berinteraksi dengan aplikasi adalah kegunaannya yang umum.

Karena program dibatasi untuk melihat content memory ruang kernel, program harus menggunakan API untuk lakukan komunikasi dengannya. Walau secara teknis ada beberapa cara untuk melakukannya, yang umum ialah membuat file perangkat.

Anda mungkin pernah berinteraksi dengan file perangkat sebelumnya. Perintah yang memakai /dev/zero, /dev/null, atau sama berinteraksi dengan perangkat bernama "zero" dan "null" yang mengembalikan nilai yang diharapkan.

Dalam contoh kami, kami akan mengembalikan "Hallo, World". Walau ini bukanlah fungsi yang paling bermanfaat untuk menyediakan program, tetapi akan memperlihatkan proses menanggapi program lewat file perangkat.

Berikut daftar lengkap kami:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“Robert W. Oliver II”);
MODULE_DESCRIPTION(“A simple example Linux module.”);
MODULE_VERSION(“0.01”);#define DEVICE_NAME “lkm_example”
#define EXAMPLE_MSG “Hello, World!\n”
#define MSG_BUFFER_LEN 15/* Prototypes for device functions */
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);static int major_num;
static int device_open_count = 0;
static char msg_buffer[MSG_BUFFER_LEN];
static char *msg_ptr;/* This structure points to all of the device functions */
static struct file_operations file_ops = {
 .read = device_read,
 .write = device_write,
 .open = device_open,
 .release = device_release
};/* When a process reads from our device, this gets called. */
static ssize_t device_read(struct file *flip, char *buffer, size_t len, loff_t *offset) {
 int bytes_read = 0;
 /* If we’re at the end, loop back to the beginning */
 if (*msg_ptr == 0) {
 msg_ptr = msg_buffer;
 }
 /* Put data in the buffer */
 while (len && *msg_ptr) {
 /* Buffer is in user data, not kernel, so you can’t just reference
 * with a pointer. The function put_user handles this for us */
 put_user(*(msg_ptr++), buffer++);
 len--;
 bytes_read++;
 }
 return bytes_read;
}/* Called when a process tries to write to our device */
static ssize_t device_write(struct file *flip, const char *buffer, size_t len, loff_t *offset) {
 /* This is a read-only device */
 printk(KERN_ALERT “This operation is not supported.\n”);
 return -EINVAL;
}/* Called when a process opens our device */
static int device_open(struct inode *inode, struct file *file) {
 /* If device is open, return busy */
 if (device_open_count) {
 return -EBUSY;
 }
 device_open_count++;
 try_module_get(THIS_MODULE);
 return 0;
}/* Called when a process closes our device */
static int device_release(struct inode *inode, struct file *file) {
 /* Decrement the open counter and usage count. Without this, the module would not unload. */
 device_open_count--;
 module_put(THIS_MODULE);
 return 0;
}static int __init lkm_example_init(void) {
 /* Fill buffer with our message */
 strncpy(msg_buffer, EXAMPLE_MSG, MSG_BUFFER_LEN);
 /* Set the msg_ptr to the buffer */
 msg_ptr = msg_buffer;
 /* Try to register character device */
 major_num = register_chrdev(0, “lkm_example”, &file_ops);
 if (major_num < 0) {
 printk(KERN_ALERT “Could not register device: %d\n”, major_num);
 return major_num;
 } else {
 printk(KERN_INFO “lkm_example module loaded with device major number %d\n”, major_num);
 return 0;
 }
}static void __exit lkm_example_exit(void) {
 /* Remember — we have to clean up after ourselves. Unregister the character device. */
 unregister_chrdev(major_num, DEVICE_NAME);
 printk(KERN_INFO “Goodbye, World!\n”);
}/* Register module functions */
module_init(lkm_example_init);
module_exit(lkm_example_exit);

Testing Our Enhanced Example

Sekarang contoh kita melakukan suatu hal yang lebih dari sekedar mencetak pesan saat load and unload, kita memerlukan rutinitas pengetesan yang tidak begitu strict. Mari ganti Makefile kita supaya hanya memuat modul dan don't disassemble it.

obj-m += lkm_example.oall:
  make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:
  make -C /lib/modules/$(shell uname -r)/build M=$(PWD) cleantest:
  # We put a — in front of the rmmod command to tell make to ignore
  # an error in case the module isn’t loaded.
  -sudo rmmod lkm_example
  # Clear the kernel log without echo
  sudo dmesg -C
  # Insert the module
  sudo insmod lkm_example.ko
  # Display the kernel log
  dmesg

Sekarang saat Anda menjalankan "make test", Anda akan melihat output dari nomor utama perangkat. Dalam contoh kami, ini dengan cara otomatis ditetapkan oleh kernel. Tetapi, Anda membutuhkan nilai ini untuk membuat perangkat.

Mengambil nilai yang Anda dapatkan dari "make test" dan gunakan untuk membuat file perangkat hingga kita bisa berkomunikasi dengan modul kernel kita dari ruangan pengguna.

sudo mknod /dev/lkm_example c MAJOR 0

(dalam contoh di atas, ubah MAJOR dengan nilai yang Anda dapat dari “make test” atau “dmesg”)

"C" dalam perintah mknod memberi tahu mknod bahwa kita perlu membuat file perangkat karakter.

Sekarang kita bisa mengambil konten dari perangkat:

cat /dev/lkm_example

atau bahkan melalui perintah "dd":

dd if=/dev/lkm_example of=test bs=14 count=100

Anda dapat terhubung perangkat ini lewat program. Mereka tidak harus berupa aplikasi yang dikompilasi — bahkan juga skrip Python, Ruby, dan PHP bisa mengakses data ini.

Setelah selesai dengan perangkat, hapus dan bongkar modul:

sudo rm /dev/lkm_example
sudo rmmod lkm_example

Ringkasan

Saya berharap Anda enjoy our romp through the land of kernels. Walau contoh yang saya beri ialah dasar, Anda bisa menggunakan struktur ini untuk membuat modul Anda sendiri yang melakukan pekerjaan yang sangat kompleks.

Ingatlah jika Anda sepenuhnya sendirian di land of kernels. Tidak ada backstop atau peluang kedua untuk code Anda. Bila Anda mengutip project untuk client, pastikan untuk doubling, bila tidak 3x lipat, waktu debug yang diperkirakan. Code kernel harus sesempurna mungkin untuk memastikan kredibilitas dan keunggulan sistem yang akan menjalankannya.