Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Pisanje kernel modula - Linux Kernel Module Programming
#1
Brick 
Ovih dana sam experimentisao malo sa pisanjem Linux kernel modula (device driver) i dostavljam ovde jedan prost primer kao osnovu - skeleton.

Ovde pokazujem jedan primer za "Character Device" (suprotno od "Block Device") driver, parce dinamickog programa koji se ucitava u kernel i koji operise sa tkz seriskim tipovima uredjaja, npr raznim komunikacioni portovi poput RS232, I2C, USB i slicno.
Primetice te da nije uradjena fizicka komunikacija vec je ceo drajver napravljen kao "fake", tj moze u njega da se upisuje i posle da se iscitavaju podaci, kao da imamo prikacenu neku serisku memoriju od 100 bajtova ("virtual_device").

Razvojno okruzenje je Linux Debian 8 sa potrebnim paketima (gcc, make, linux-headers …)

hello.c C++ source code (zanemarite PHP, iskorisceno za forum samo za highligh sintakse)

PHP Code:
/**********************************************************
***********************************************************
hello.c
***********************************************************
***********************************************************/

#include <linux/init.h> //MUST
#include <linux/module.h> //MUST
#include <linux/moduleparam.h> //to pass params
#include <linux/fs.h> //File system operations, read/write, open/close device drivers
#include <linux/cdev.h> //characher device driver
#include <linux/semaphore.h> //used to access semaphores; sunchronization behaviors
#include <asm/uaccess.h> //copy_to_user; copy_from_user

//other variables
struct cdev *mcdev//my charachter device
int major_number//will store our major number - extracted from dev_t using macro - mknod /directory/files c major minor
int ret//used to hold return values of functions; this is because kernel stack is very small so declare variables all over the pass in our module functions eats up the stack very fast
dev_t dev_num// hold major naumber that kernel give us; name --> apears in /proc/devices

#define DEVICE_NAME "mikidev"

//structure for device
struct fake_device {
    
char data[100];
    
struct semaphore sem;
virtual_device;


//***********************************************************/
//***********************************************************/
//***********************************************************/

//pass the params (not used now)
int my_param 0;
module_param(my_paramintS_IRUSR S_IWUSR);


//***********************************************************/
//********************** device operation  ******************/
//***********************************************************/

//called on device_file open
//inode reference to the file on disk
//and contains information about the file
//struct files is represents an abstract open file
int device_open (struct inode *inodestruct file *filp) {

    
//only allow one process to open this device by using semaphore as mutual exclusive lock - mutex
    
if (down_interruptible(&virtual_device.sem) != 0) {
        
printk(KERN_ALERT "%s: could not lock device during open"DEVICE_NAME);
        return -
1;
    }

    
printk(KERN_INFO "mikidev: opened device");
    return 
0;

}

ssize_t device_read (struct filefilpcharbufStoreDatasize_t bufCountloff_tcurOffset) {
    
//take the kernel space (device) to user space (process)
    //copy_to_user (destiantion, source, sizeToTransfer)
    
printk(KERN_INFO "%s: Reading from device"DEVICE_NAME);
    
ret copy_to_user(bufStoreDatavirtual_device.databufCount);
    return 
ret;
}

ssize_t device_write (struct filefilp, const charbufSourceDatasize_t bufCountloff_tcurOffset) {
    
//send data from user to kernel
    //copy_from_user (destiantion, source, sizeToTransfer)
    
printk(KERN_INFO "%s: Writng to device"DEVICE_NAME);
    
ret copy_from_user(virtual_device.databufSourceDatabufCount);
    return 
ret;
}

int device_close(struct inode *inodestruct file *filp) {
    
//by calling up, whic is opposite of down for semaphore, we realease the mutex we obtained ad device open
    //this has the effect of allowing other process to use the device now
    
up(&virtual_device.sem);
    
printk(KERN_INFO "%s: closed device"DEVICE_NAME);
    return 
0;
}

//tell the kernel which functions to call when user operates on our device file
struct file_operations fops = {
    .
owner THIS_MODULE//prevent unloading of this module when operations are in use
    
.open device_open//points to method to call when opening the device
    
.release device_close//points to method to call when closing the device
    
.write device_write//points to method to call when writing the device
    
.read device_read //points to method to call when reading the device
};


//***********************************************************/
//********************** module operation *******************/
//***********************************************************/
static int driver_init (void) {
    
    
//register our device with the system: a two step process
    //step 1 use dynamic alloacation to assign our device a major number 
    // -- alloc_chrdev_region (dev_t*, uint fminor, uint count, char* name)
    
ret alloc_chrdev_region (&dev_num0,1DEVICE_NAME);
    if (
ret 0) {//alert time kernel functions return negatives, there is an error
        
printk(KERN_ALERT "%s: faild to allocate a major number"DEVICE_NAME);
        return 
ret//propagate error
    
}
    
    
major_number MAJOR(dev_num); //extract the major number and store into our variables (MACRO)
    
printk(KERN_INFO "%s: major number is %d"DEVICE_NAMEmajor_number);
    
printk(KERN_INFO "\tuse \"mknod /dev/%s c %d 0\" for device file"DEVICE_NAMEmajor_number); //dmesg
    
    
mcdev cdev_alloc(); //create our cdev structure, initialize our cdev
    
mcdev->ops = &fops//struct file_operations
    
mcdev->owner THIS_MODULE;

    
//now we created cdev, we have to add it to the kernel
    //int cdev_add(struct cdev* dev, dev_t num, unsigned int count)
    
ret cdev_add(mcdevdev_num1);
    if (
ret 0) { //always check errors
        
printk(KERN_ALERT "%s: unable to add cdev to kernel"DEVICE_NAME);
        return 
ret;
    }
    
    
//initialise our semaphore
    
sema_init (&virtual_device.sem1); //initial values of one
    
    
return 0;
}

static 
void driver_exit (void) {

    
//unregister evrything in reverse order
    
cdev_del(mcdev);
    
    
unregister_chrdev_region(dev_num1);
    
printk(KERN_ALERT "%s: unloaded module"DEVICE_NAME);

}

//***********************************************************/
//********************** init *******************************/
//***********************************************************/
//inform the kernel where to start and stop with our module/driver
module_init (driver_init);
module_exit (driver_exit);

MODULE_AUTHOR("A. Markovic");
MODULE_DESCRIPTION("My first kernel module");
MODULE_LICENSE("GPL"); 

Makefile (skripta za utomatsko kompajliranje)

PHP Code:
obj-:= hello.o


KERNEL_DIR 
= /usr/src/linux-headers-$(shell uname -r)

all:
    $(
MAKE) -$(KERNEL_DIRSUBDIRS=$(PWDmodules
    
clean
:
    
rm -rf *.*.ko *.mod.* *.symvers *.order 

U ovom Makefile vidite da se poziva hederi za trenutni OS na kome radite ali ako tu explicitno navedete neku drugu verziju hedera, recimo specificnu za RPi, moze bez problema da se iskompalira i za druge procesore.

Koga ovo bude interesovalo neka slobodno postavi pitanja Smile

Sve ovo je opisano u odlicnom YT tutorialu (8 delova):
https://www.youtube.com/watch?v=-O6GsrmOUgY

Nakon uspesnog kompajliranja (poziva se "make"), treba da dobijete hello.ko fajl koji predstavlja sam driver.
Drajver se ucitava u kernel sa komandom "insmod hello.ko";
Tokom ucitavanja sam drajver ce ispisati nekoliko poruka i te poruke mogu da se vide u syslog (dmesg);
Iskljucivanje / deaktivacija drajvera se radi komandom "rmmod hello".


Attached Files
.c   hello.c (Size: 5,71 KB / Downloads: 1)
Reply
#2
Za RPi posto je ARM mora da se koristi cross-compiler ako se radi sa PC-a, ne moze samo ovako. Ne znam koji ima za RPi, za BeagleBone (Cortex-A8) ima recimo Linaro pa se za make navodi jos
CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm
inace ce da ga kompajlira za host arhitekturu koja god verzija headera da je. 


Interesantna tema u svakom slucaju, pridruzujem se diskusiji ako i ja mogu nekako da pomognem Smile
Reply
#3
Ja sam (cross) kompaliranje kernela za RPi radio sa buildroot + busybox pod Debian. To je minimalisticko okruzenje, root FS je negde oko 40MB.
Sa tim se dobije kompletan image koji se ucita na SD karticu. Iskompalirao sam neku verziju ali nisam jos stigao da probam (kompajlirao jedno 4-5 sati).
Reply
#4
(06-16-2015, 11:40 AM)mikikg Wrote: Ja sam (cross) kompaliranje kernela za RPi radio sa buildroot + busybox pod Debian. To je minimalisticko okruzenje, root FS je negde oko 40MB.
Sa tim se dobije kompletan image koji se ucita na SD karticu. Iskompalirao sam neku verziju ali nisam jos stigao da probam (kompajlirao jedno 4-5 sati).

Obavezno odgledajte ovaj video vezano za Buildroot

https://www.youtube.com/watch?v=0LJHx09R...e=youtu.be
Reply
#5
Pojavio se jedan vrlo zanimljiv modul koji vredi pogledati.

U pitanju je vrlo "mocan" mini racunar koji ima 18 procesorskih jezgra, 2 ARM A9 jezgra i 16 RISC komprocesora + 28k FPGA celija za dodatne funkcije!
Cena je vrlo vrlo pristupacna za takvu spravu!

   

https://www.parallella.org/board/

Quote:The Parallella Board
• 18-core credit card sized computer
• Gigabit Ethernet
• 1GB SDRAM
• Micro-SD storage
• HDMI, USB (optional)
• Up to 48 GPIO pins
• User configurable hardware (FPGA)
• Open source design files
• #1 in energy efficiency
• Starting at $99

Kako li ce na ovome da radi neki kernel module? Smile
BTW: Da li znate da je "zabranjeno" da se koristi Floating-point matematika u kernelu? Smile

https://www.youtube.com/watch?v=RHTZ3CLOlqw

https://www.youtube.com/watch?v=BHZCCUEzK0s
Reply
#6
Miki, ovo izgleda veoma, veoma ozbiljno. Prisustvo FPGA otvara neverovatno puno prostora za namene koje su do sada bile teško izvodljive.

Inače, iz posrednog iskustva znam da je pod standardnim Linuxom, veoma teško dobiti real-time rad. Potrebno je dosta promena, ubacivanje "pred kernel" sistema koji bi presretao signale itd.

Zato se kod RPy veoma često sreće neki MCU dodatak koji radi vremenski zahtevne stvari, a RPy sve ostalo.

Ovaj FPGA bi taj problem prilično rešio, a da ne pričamo o brzini signala koje može FPGA da "sažvaće".
Reply
#7
Da svi ti linux bazirani sistemi koji nisu hard-real-time imaju malo problema sa nestabilnim tajmingom i relativno losim response-time.
Ti klasicni linuxi spadaju u soft-realtime sto znaci "nista lose se nece desiti ako nesto malo zakasni" dok je hard-realtime vrlo striktno orijentisan za precizne vremenske dogadjaje i vrlo brzim response/latencom i ako nesto kojim slucajem zakasni "desice se lose stvari" (mehanicki kvar masine i slicno).

Ovaj spomenuti Parallela racunar je zgodan prvo sto ima dodatna RISC jezgra gde mogu da se pokrenu delovi programa koji se bave tim striktnim vremenskim pojavama i to bi bilo neko ekvivaletno ponasanje klasicnim PIC/AVR procesorima. Kod njih se recimo meri vreme latancije (odgov na spoljnje promene/dogadjaje) u stotinama nano sekundi.

Kome je i ovih par stotina nano sekundi sporno onda se ide na neprikosnovene FPGA celije gde se radni clock krece u rangu oko 500MHz za spomenut Parallela racunar i latenciju u desetinama nano sekundi.

Gledao sam neke video prezentacije i radili su testove sa racunom za pronalazenje prostih brojeva (prime numbers) i krajnji rezultat je bio da je Parallela bila u klasi CISC procesora ranga Intel Core2Duo!!! Bez koriscenja RISC jezgra rezultat je bio nesto 1000x losiji/sporiji. E sad koliko kosta CISC racunar i koliko struje trosi (samo C2D trosi minimum 50W, gde je memorija i ostalo) a ovo cudo od Parallele citavih 5W.

U najavi im je za sledecu godinu i model sa 64 RISC jezgra i vecim brojem FPGA celija!
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)