Temel Modül Fonksiyonları ve Kavramlar
Kernel modül yolculuğumuza devam ediyoruz.
printk(), pr_x()
Kernel modüller de daemon’lar gibi ekrana değil log dosyalarına yazar. Log
üretmek için printk() fonksiyonunu kullanırız. k, kernel demektir.
Kullanımı printf() gibidir. Varsayılan durumda bu fonksiyon mesajların
/var/log/syslog dosyasına yazdırılmasını sağlar. Ama artık Debian 13 gibi
modern sistemlerde bu dosya bulunmaz, onun yerine systemd tabanlı
journal kullanılır. Biz
çalışmalar yaparken sudo dmesg -W ile yeni logları takip edebiliriz arkadaki
sistemin detaylarına takılmadan. Ya da dmesg --human vs.
Fonksiyon<linux/kernel.h> içerisindedir.
Kullanımı şu şekildedir:
printk(KERN_INFO "Merhaba Dunya!\n");
Burada virgül yoktur, dikkat. KERN_INFO bir macrodur.
Bakalım:
5#define KERN_SOH "\001" /* ASCII Start Of Header */
6#define KERN_SOH_ASCII '\001'
7
8#define KERN_EMERG KERN_SOH "0" /* system is unusable */
9#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
10#define KERN_CRIT KERN_SOH "2" /* critical conditions */
11#define KERN_ERR KERN_SOH "3" /* error conditions */
12#define KERN_WARNING KERN_SOH "4" /* warning conditions */
13#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
14#define KERN_INFO KERN_SOH "6" /* informational */
15#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */
Burada ASCII SOH karakteri kullanılmaktadır. C’deki string concatenation
kuralları ile birleşmektedir. O yüzden virgül kullanmamak lazım.
Bunun yerine doğruda pr_info(), pr_err() gibi makroları da kullanabiliriz.
pr_info("Merhaba Dünya") gibi.
576/**
577 * pr_info - Print an info-level message
578 * @fmt: format string
579 * @...: arguments for the format string
580 *
581 * This macro expands to a printk with KERN_INFO loglevel. It uses pr_fmt() to
582 * generate the format string.
583 */
584#define pr_info(fmt, ...) \
585 printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
Tekrar hatırlayalım: Kernel modüller kernelin içerisine yerleştirildiği için biz
user mod’daki kütüphaneleri kullanamayız. Örneğin standart C ve POSIX
fonksiyonlarını kullanamayız. Çünkü bunlar, user mode programlar için
oluşturulmuş kütüphanelerin içerisindedir. Örneğin printf() fonksiyonu, kernel
modülleri tarafından kullanılamaz.
Biz kernel modüllerinin içerisinde yalnızca export edilmiş kernel fonksiyonlarını kullanabiliriz. Örneğin buradaki gibi yerlerden bakabiliriz. Fakat dokümantasyon mükemmel değildir, kaynak koda bakmak gerekebilir.
/proc/modules ve lsmod
Yüklü olan modülleri procfs içerisinde görebiliriz. /proc/modules dosyasına
bakarak yüklü olan modülleri görebiliriz. Dosyanın her satırında bir kernel
modül bilgisi vardır.
Örnek:
$ grep helloworld /proc/modules
helloworld 12288 0 - Live 0x0000000000000000 (OE)
Bizim helloworld modülümüz şu anda kernelde yüklü.
Benzer şekilde lsmod komutu ile de listeleyebiliriz. Bu komut da zaten
bilgileri bu dosyadan almaktadır.
İşte kanıtımsı çıktı:
$ sudo strace lsmod 2>&1 | grep -C 3 "/proc/modules"
read(3, "BOOT_IMAGE=/vmlinuz-6.12.57+deb1"..., 4095) = 82
read(3, "", 4013) = 0
close(3) = 0
openat(AT_FDCWD, "/proc/modules", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "helloworld 12288 0 - Live 0xffff"..., 1024) = 1024
read(3, "d_hda_intel,snd_hda_codec, Live "..., 1024) = 1024
lsmod daha güzel formatlama yapabilmektedir.
$ sudo lsmod | grep helloworld
helloworld 12288 0
Diğer Modüller ?
Eğer lsmod ya da /proc/modules ile bakarsanız aslında birçok kernel modülü
olduğunu görebilirsiniz. Mesela
$ wc -l /proc/modules
96 /proc/modules
Bende 96 adet kernel modülü varmış o anda yüklü. Tamam, bir tanesi helloworld
ama 95 adet de az değil hani!
Modüller boot sırasında yüklenebilir, işletim sisteminin bir parçası olarak yüklenebilir ya da bizim yaptığımız gibi daha sonradan yüklenebilir. Debian, Ubuntu, Fedora gibi genele hitap eden desktop dağıtımlarda kernel’i minimal tutup, statik kısmını, birçok özelliği modül olarak yüklemek başta sayı olarak fazla gözükse de günümüzde normal durumdadır. Gömülü bir Linux dağıtımı yapıyor olsaydık daha makul seviyelerde modül sayımız olabilirdi.
module_init() ve module_exit()
ELF formatı section’lardan oluşuyor. Şimdi bunların üzerinde bir duralım.
Öncelikle kaynak kodumuzu hatırlayalım:
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
int init_module(void){
printk(KERN_INFO "Merhaba Dunya...\n");
return 0;
}
void cleanup_module(void) {
printk(KERN_INFO "Gule gule Dunya...\n");
}
Bunu derlediğimiz zaman çıkan dosya helloworld.ko olsun, onu biraz analiz
edelim.
$ nm -n helloworld.ko | grep -E 'init_module|cleanup_module'
0000000000000000 T __pfx_init_module
0000000000000010 T init_module
0000000000000030 T __pfx_cleanup_module
0000000000000040 T cleanup_module
Her iki fonksiyonumuz da .text section içerisinde bulunuyor, yanlış
bilmiyorsam T bu anlamda.
Alternatif:
$ readelf -S helloworld.ko
There are 48 section headers, starting at offset 0x291c8:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000000 0000000000000000 AX 0 0 1
[ 2] .text.unlikely PROGBITS 0000000000000000 00000040
0000000000000055 0000000000000000 AX 0 0 16
Index 1, .texte ait, 2 de öyle ama nadir çalışacak fonksiyonlar burada,
derleyici bu şekilde çalışma sırasında cache locality vs iyileştirmek için
bunları yapabiliyor, ama bu da .text alanı aslında.
$ readelf -Ws helloworld.ko | grep -E 'init_module|cleanup_module'
44: 0000000000000040 21 FUNC GLOBAL DEFAULT 2 cleanup_module
46: 0000000000000010 28 FUNC GLOBAL DEFAULT 2 init_module
49: 0000000000000000 16 FUNC GLOBAL DEFAULT 2 __pfx_init_module
50: 0000000000000030 16 FUNC GLOBAL DEFAULT 2 __pfx_cleanup_module
Bizim init ve exit fonksiyonlarımız 2 indeksli section’da yani
.text.unlikely yani aslında .text içerisinde.
Burada ilginç bir şey yok
Biz istersek, ki artık önerilen budur, modülün giriş ve çıkış fonksiyonlarına
rastgele isimler verip, bunları module_init() ve module_exit() makroları
ile tanıtabiliriz. Eski kernellerde init_module() ve cleanup_module()
kullanmak zorunluydu.
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
int merhaba(void){
printk(KERN_INFO "Merhaba Dunya...\n");
return 0;
}
void gulegule(void) {
printk(KERN_INFO "Gule gule Dunya...\n");
}
module_init(merhaba);
module_exit(gulegule);
Bu kodu derlediğimi zaman, bu iki fonksiyonun da yine .text section’u içinde
kaldığını görebiliriz.
Bu makro içerisindeki fonksiyonların öncesinde bildirilmiş olması gerekmektedir. O yüzden tipik olarak kodun en sonuna yazılırlar.
Bu iki macro şuna benzemektedir:
80#ifndef MODULE
81/**
82 * module_init() - driver initialization entry point
83 * @x: function to be run at kernel boot time or module insertion
84 *
85 * module_init() will either be called during do_initcalls() (if
86 * builtin) or at module insertion time (if a module). There can only
87 * be one per module.
88 */
89#define module_init(x) __initcall(x);
90
91/**
92 * module_exit() - driver exit entry point
93 * @x: function to be run when driver is removed
94 *
95 * module_exit() will wrap the driver clean-up code
96 * with cleanup_module() when used with rmmod when
97 * the driver is a module. If the driver is statically
98 * compiled into the kernel, module_exit() has no effect.
99 * There can only be one per module.
100 */
101#define module_exit(x) __exitcall(x);
Internal Linkage, static
C’de bir fonksiyon varsayılan olarak external linkage’a sahip olmaktadır. C’de
namespace kavramı olmadığı için fonksiyonların isimlerinin çakışmasını
önlemek, başımızın daha az ağırmasını sağlamak için olabilecek her yerde
modüller içerisinde fonksiyonların static olarak tanımlanması önerilir.
Aynısı global değişkenler için de geçerlidir.
Her ne kadar C’de namespace ya da scope özelliği yoksa da kernel modüllerinin ele alınış biçiminden dolayı problem çıkartmak çok da kolay değildir. Çünkü kernel modüllerin derlenmiş halleri aslında ELF dosyalarıdır ve modül yüklendiği zaman buralardaki sembollerin çalışan kernel sembolleri ile hemen çakışmasını beklemeyiz. Modüller bu açıdan biraz ayrıcalıklıdır, bir nevi ayrı bir program olarak yüklenir gibi düşünebiliriz sembol çözümleme vs açısından. AMA birçok sürücü/driver modül olarak derlendiği gibi statik olarak da kernel ile beraber derlenebilir. İşte bu durumda isim çakışmaları problem olacaktır.
Özetle, mümkün olduğunca static kullanmalıyız.
__init ve __exit
__init ve __exit birer makrodur.
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/init.h
adresinden tanımlamalarına bakabiliriz:
#define __init __section(".init.text") __cold __latent_entropy \
__no_kstack_erase
//...
#define __exit __section(".exit.text") __exitused __cold notrace
Görüldüğü üzere derleyici spesifik eklemeler ile fonksiyonların ELF dosyasında bulunacağı yerleri işaret ederler aslında.
Bunun çeşitli avantajları olabilir. Mesela __init ile işaretlenmiş fonksiyon,
normalde init fonksiyonu, modül init’i bittikten sonra bellekten yer açmak için
kernel tarafından atılabilir. Çünkü biz bu fonksiyonunun sadece init sırasında
çağırılacağını belirttik. Benzer şekilde __exit fonksiyonu da eğer kernelin
modül çıkarma, rmmod gibi, özelliği kapalıysa ya da modül doğrudan kernel
içerisine derleniyorsa (bu durumda kernel kapanana kadar modül tek başına çıkış
yapamaz zaten) hiç konmayabilir. Bu iki makro, bu tarz optimizasyonların
yapılmasına imkan sağlamaktadır.
Bazen kernel açılırken,
Freeing unused kernel memory: 236k freed
gibi loglar görebiliriz. İşte bu tarz bellekte yer açmalar buralardan olur.
Hepsini birleştirdiğimizde modülümüz aşağıdaki gibi olmaktadır:
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
static int __init merhaba(void){
printk(KERN_INFO "Merhaba Dunya...\n");
return 0;
}
static void __exit gulegule(void) {
printk(KERN_INFO "Gule gule Dunya...\n");
}
module_init(merhaba);
module_exit(gulegule);
Üstte belirtildiği gibi bakarsak bu sefer merhaba() fonksiyonun .text
içerisinde değil .init.text içerisinde; gulegule() fonksiyonunun ise
.exit.text içerisinde olduğunu görürüz.
sysfs ve Modüller
2003 yılında kernel 2.6 ile beraber sysfs dosya sistemi gelmiştir. procfs
daha eski bir yapıdır ve sysfs, procfsteki çeşitli eksiklikleri gidermek
için eklenmiştir. Güncel sistemlerde her iki dosya sistemi de bulunmaktadır
tipik olarak. Her iki dosya sistemi de çekirdekle ilgili bilgileri dışarıya
açmak ya da dışarıdan değiştirilmesine imkan sağlamak için vardır. sysfs,
biraz da nesne yönelimli yaklaşımı benimser, daha derin hiyerarşide birçok
dosya ve dizin barındırır. sysfs, tipik olarak /sys altına mount edilir.
Bir bir modül yüklediğimi zaman modül ile aynı isimde /sys/module/<isim>
bir dizin yaratılır. Burası /proc/modulesten farkldır. sysfs çok daha
detaylı bilgi verir.
Bizim modülümüz için bakalım:
$ ls -lah /sys/module/helloworld/
total 0
drwxr-xr-x 5 root root 0 Dec 20 19:53 .
drwxr-xr-x 190 root root 0 Dec 20 14:31 ..
-r--r--r-- 1 root root 4.0K Dec 20 19:53 coresize
drwxr-xr-x 2 root root 0 Dec 20 19:53 holders
-r--r--r-- 1 root root 4.0K Dec 20 19:53 initsize
-r--r--r-- 1 root root 4.0K Dec 20 19:53 initstate
drwxr-xr-x 2 root root 0 Dec 20 19:53 notes
-r--r--r-- 1 root root 4.0K Dec 20 19:53 refcnt
drwxr-xr-x 2 root root 0 Dec 20 19:53 sections
-r--r--r-- 1 root root 4.0K Dec 20 19:53 taint
--w------- 1 root root 4.0K Dec 20 19:53 uevent
129-11920