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)
Makefile (skripta za utomatsko kompajliranje)
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
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".
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_param, int, S_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 *inode, struct 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 file* filp, char* bufStoreData, size_t bufCount, loff_t* curOffset) {
//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(bufStoreData, virtual_device.data, bufCount);
return ret;
}
ssize_t device_write (struct file* filp, const char* bufSourceData, size_t bufCount, loff_t* curOffset) {
//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.data, bufSourceData, bufCount);
return ret;
}
int device_close(struct inode *inode, struct 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_num, 0,1, DEVICE_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_NAME, major_number);
printk(KERN_INFO "\tuse \"mknod /dev/%s c %d 0\" for device file", DEVICE_NAME, major_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(mcdev, dev_num, 1);
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.sem, 1); //initial values of one
return 0;
}
static void driver_exit (void) {
//unregister evrything in reverse order
cdev_del(mcdev);
unregister_chrdev_region(dev_num, 1);
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-m := hello.o
KERNEL_DIR = /usr/src/linux-headers-$(shell uname -r)
all:
$(MAKE) -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules
clean:
rm -rf *.o *.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
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".