יום שבת, 26 באפריל 2014

Linked List Linux Kernel


בהמשך למסע ב Kernel החלטתי להתמקד בקצרה על רשימות מקושרות, למי שלא מכיר או שהספיק לשכוח מדובר על דרך לחבר בין אובייקטים בעזרת מצביעים לאובייקטים אחרים בזיכרון, לכל אובייקט יש מצביע לאובייקט שלפניו ומצביע לאובייקט שאחריו כפי שניתן לראות בתמונה:



שימו לב!

  • עבודה מול ה Kernel עלולה להזיק למחשב שלך, מומלץ לעבוד על תחנה וירטואלית.
  • המאמר נכתב על Fedora 14.

מערכת ההפעלה נותנת כלים פנימיים שעוזרים לנהל את הרשימות במקום שנהל בעצמנו, קובץ list.h חושף מספר פונקציות ו Macros כפי שניתן לראות בדוגמה:


myList.c

//define module
#include <linux/module.h>
//we are in the kerenl
#include <linux/kernel.h>
//linked list header
#include <linux/list.h>

//example struct to be linked
struct element {
int id;
char name[15];
char description[50];

//creating header, next and previous pointers
struct list_head list;
};

//the list!
struct element elementsList;

void addElements(void)
{
struct element *tmp;

int listMax = 20;
int i = 0;

for(i = 0; i < listMax; i++)
{
//getting memory for the struct
tmp = kmalloc(sizeof(*tmp),GFP_KERNEL);
tmp->id = i;
strcpy(tmp->name, "Element");
strcpy(tmp->description, "just simple element inside the list");

//initializes pointers
INIT_LIST_HEAD(&tmp->list);
//add the element at the end of the list
  list_add_tail(&(tmp->list), &(elementsList.list));

}

printk(KERN_INFO "load complete\n");
}

void deleteElements(void)
{
struct element *tmp, *node;
  //if deleting elements better using this function,
  //it preserve the pointer to the next element,preventing holes inside the list. 
  list_for_each_entry_safe(node, tmp, &elementsList.list, list){
         //remove from the list
         list_del(&node->list);
         //release the memory
         kfree(node);
    }
}

void viewElements(void)
{
struct element *tmp;

//running through all elements
list_for_each_entry(tmp, &elementsList.list, list) {
printk(KERN_INFO "Element\n ID: %d; Name: %s; 
                         Description: %s\n", tmp->id, tmp->name, tmp->description);
}
}

int c_module() {

//initializes the list
INIT_LIST_HEAD(&elementsList.list);
printk(KERN_INFO "initialize list complete\n");
addElements();
viewElements();
return 0;

}

void r_module()
{
printk(KERN_INFO "delete list\n");
deleteElements();
printk(KERN_INFO "remove module complete\n");
}

module_init(c_module);
module_exit(r_module);



מגדירים מבנה עם אובייקט מיוחד מסוג list_head שמכיל בתוכו את המצביעים, מייצרים מופע כללי של המבנה שמייצג את ראש הרשימה, בשלב טעינת ה Module  נטענת הפונקציה addElements שמכניסה 20 אובייקטים לרשימה, היא מאפסת את המצביעים ושולחת כל מבנה לפונקציה (list_add_tail) שמוסיפה אותו לסוף הרשימה.

לסיום רצה הפונקציה viewElements שמציגה את הרשימה בעזרת המאקרו list_for_each_entry שדרכו ניתן לעבור על האלמנטים, בהסרת ה Module רצה הפונקציה deleteElements שמפעילה את המאקרו list_for_each_entry_safe שדואג להסרה בטוחה של אובייקטים מבלי שהיו חורים ברשימה.

סיכום

מדובר בכלי חזק אבל קצת מסורבל שעלול לגרום לכאב ראש לא קטן אם ננסה לממש אותו בעצמנו, Linux חוסכת את כל ההתעסקות הכואבת במצביעים ונותנת תשתית נוחה לבניה ולניהול רשימות מקושרות.

מידע נוסף


רשמת?




יום שבת, 12 באפריל 2014

Procsfs Linux Kernel



מערכת Linux נותנת מספר דרכים להעברת מידע בין ה User Space ל Kernel, אחת הדרכים הצגתי במאמר הפתיחה בעזרת Character Device שיוצר קובץ בתיקיית ה Dev דרכו מדברים עם ה Module, השיטה הנוספת שהחלטתי להתמקד עליה היא Procs File System שמאוד דומה רק שהקובץ ממוקם בספריית ה Proc ללא צורך רישום כ Device.

הקובץ פותח שער שדרכו כותבים וקוראים מה Module בדומה לעבודה על קבצים (פתיחה,כתיבה,קריאה וסגירה) וכך נוצר ממשק אחיד, פשוט ונוח, לצורך הדוגמה כשנריץ את הפקודה Cat על הקובץ cpuinfo בתקיית proc נקבל את הפלט הבא:

[root@localhost proc]# cat cpuinfo 
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 26
model name : Intel(R) Core(TM) i7 CPU         950  @ 3.07GHz
stepping : 5
cpu MHz : 3158.658
cache size : 6144 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 5
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx lm constant_tsc up pni monitor ssse3 lahf_lm
bogomips : 6317.31
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:

שימו לב!
  • עבודה מול ה Kernel עלולה להזיק למחשב שלך ממולץ לעבוד על תחנה וירטואלית.
  • המאמר נכתב על Fedora 14.

מייצרים אובייקט מסוג proc_dir_entry שמייצג את הקובץ בתיקיית ה Proc, לאחר מכן דורסים את הפונקציות Read ו Write של הקובץ לפונקציות פנימיות ב Module, מגדירים Buffer עבור המידע שיעבור ואפשר להגיד שסיימנו.

procModule.c

//define module.
#include <linux/module.h>
//we are in the kernel.
#include <linux/kernel.h>
//proc header.
#include <linux/proc_fs.h>
//copy from user.
#include <asm/uaccess.h>

#define PROCMAXSIZE 4096
#define PROC_NAME "Bridge"

//pointer to the proc file.
static struct proc_dir_entry *procfile;

//buffer of the proc file.
static char procbuffer[PROCMAXSIZE];

//buffer size.
static unsigned long buffersize = 0;

//proc file new read function will overwrite the old one.
//buffer: the kernel buffer pointer.
//buffer_location: the starting point of reading
//offset:the offset from the beginning of the file.
//buffer_length: the number of chars to read.
//eof: end of file pointer if there is more to read.
//data: private data.
//return the read length value.
int procfile_read(char *buffer, char **buffer_location,off_t offset, int buffer_length, int *eof, void *data)
{
int length;

if (offset > 0) {
//nothing to read reset length read value.
length  = 0;
} else {

//copy data from kernel space to user space.
memcpy(buffer, procbuffer, buffersize);

printk(KERN_INFO "Read: %s \n", procbuffer);

//set the length read value.
length = buffersize;
}
return length;
}

//proc file new write function will overwrite the old one.
//file: open file structure.
//buffer: user space buffer.
//count: the number of chars been writes.
//data: private data.
//return the read length value.
int procfile_write(struct file *file, const char *buffer, unsigned long count, void *data)
{
//getting buffer size.
buffersize = count;

//check for overflow.
if (buffersize > PROCMAXSIZE ) {
buffersize = PROCMAXSIZE;
}

//copy data from user space buffer to kernel space buffer.
if ( copy_from_user(procbuffer, buffer, buffersize) ) {
//return error if unable.
return -EFAULT;
}

printk(KERN_INFO "Write: %s \n", procbuffer);

//return the size of writing.
return buffersize;
}


int c_module()
{
//create the proc file on module creation,
//give name and permissions.
procfile = create_proc_entry(PROC_NAME, 0644, NULL);

//if null return error handling.
if (procfile == NULL) {

remove_proc_entry(PROC_NAME, NULL);
printk(KERN_ALERT "Cannot create Proc: %s\n",PROC_NAME);
//maybe cause by out of memory.
return -ENOMEM;
}

//overwrite the original read and write functions of the file.
procfile->read_proc  = procfile_read;
procfile->write_proc = procfile_write;

printk(KERN_INFO "%s created\n", PROC_NAME);

//operation complete.
return 0;
}


void r_module()
{
//remove proc file entry from folder.
remove_proc_entry(PROC_NAME, NULL);
printk(KERN_INFO "/proc/%s removed\n", PROC_NAME);
}


module_init(c_module);
module_exit(r_module);


נעשה Compile בעזרת Makefile ונטען את ה Module לזיכרון עם הפקודה insmod, נבדוק בתיקיית ה proc שנוצר הקובץ בשם Bridge ונתחיל לדבר איתו בעזרת פקודות מה Command Line כפי שניתן לראות בדוגמה:

[root@localhost proc]# echo "send data to module" >> /proc/Bridge 
[root@localhost proc]# cat Bridge 
send data to module


סיכום

ראינו מספר דרכים לדבר עם ה Kernel בעזרת קבצים, הם מחולקים לתיקיות במערכת כמו Proc עבור ה Processes או Dev עבור Devices, יש דרכים נוספות כמו Sysfs, Debugfs ו Configfs שקצת יותר מורכבות אבל עובדות על אותו עיקרון במטרה לתת תשתית להצגה והעברת מידע נוחה ללא צורך בבנייה של ממשקים מורכבים.

בהצלחה...