अपने ड्राइवर को लिनक्स के लिए लिखना

छवि

मैं तुरंत स्वीकार करना चाहता हूं कि मैंने आपको आंशिक रूप से धोखा दिया है, क्योंकि विकिपीडिया के अनुसार, ड्राइवर एक कंप्यूटर प्रोग्राम है जिसके साथ किसी अन्य प्रोग्राम (आमतौर पर ऑपरेटिंग सिस्टम) को किसी डिवाइस के हार्डवेयर तक पहुंच मिलती है । और आज हम ड्राइवर के लिए कुछ प्रकार के रिक्त बनाएंगे, क्योंकि वास्तव में, हमने किसी भी लोहे के साथ काम नहीं किया है। आप चाहें तो इस उपयोगी कार्यक्षमता को स्वयं जोड़ सकते हैं।

आज हम जो बनाते हैं उसे अधिक सही ढंग से एलकेएम (लिनक्स कर्नेल मॉड्यूल या कर्नेल बूट मॉड्यूल) कहा जाएगा। यह कहने योग्य है कि ड्राइवर एलकेएम की किस्मों में से एक है।

हम 2.6 लाइन की गुठली के लिए मॉड्यूल लिखेंगे। 2.6 के लिए एलकेएम 2.4 से भिन्न है। मैं मतभेदों पर ध्यान नहीं दूंगा, क्योंकि यह पद के दायरे में शामिल नहीं है।

हम चरित्र उपकरण / देव / परीक्षण बनाएंगे, जिसे हमारे मॉड्यूल द्वारा संसाधित किया जाएगा। मैं तुरंत एक आरक्षण करना चाहता हूं कि चरित्र उपकरण को / देव निर्देशिका में रखना आवश्यक नहीं है, यह सिर्फ "प्राचीन जादू की रस्म" का हिस्सा है।


सिद्धांत की बिट


संक्षेप में, LKM एक ऐसी वस्तु है जिसमें पहले से चल रहे लिनक्स कर्नेल की क्षमताओं का विस्तार करने के लिए कोड होता है। यानी यह कर्नेल स्पेस में काम करता है, न कि उपयोगकर्ता। इसलिए उत्पादन सर्वर पर प्रयोग न करें। मॉड्यूल में रेंगने में त्रुटि के मामले में, कर्नेल आतंक प्राप्त करें। हम मान लेंगे कि मैंने आपको चेतावनी दी है।

एक कर्नेल मॉड्यूल में कम से कम 2 कार्य होने चाहिए: एक आरंभीकरण फ़ंक्शन और एक आउटपुट फ़ंक्शन। पहला कहा जाता है जब मॉड्यूल को कर्नेल स्थान में लोड किया जाता है, और दूसरा, जब इसे अनलोड किया जाता है। इन कार्यों को मैक्रो परिभाषाओं का उपयोग करके परिभाषित किया गया है: मॉड्यूल_इनिट और मॉड्यूल_एक्सिट

यह प्रिंटक () फ़ंक्शन के बारे में कुछ शब्द कहने के लायक है। इस समारोह का मुख्य उद्देश्य रिकॉर्डिंग की घटनाओं और चेतावनियों के लिए तंत्र को लागू करना है। दूसरे शब्दों में, यह फ़ंक्शन कर्नेल लॉग में कुछ जानकारी लिखने के लिए है।

क्योंकि चूंकि ड्राइवर कर्नेल स्पेस में चलता है, इसलिए इसे उपयोगकर्ता के एड्रेस स्पेस से सीमांकित किया जाता है। और हम एक निश्चित परिणाम देने में सक्षम होना चाहेंगे। इसके लिए, put_user () फ़ंक्शन का उपयोग किया जाता है। वह बस कर्नेल स्थान से उपयोगकर्ता के डेटा को फेंकने में लगी हुई है।

मैं चरित्र उपकरणों के बारे में कुछ शब्द कहना चाहता हूं।

कमांड ls -l /dev/sda* चलाएं। आप कुछ इस तरह देखेंगे:
brw-rw---- 1 root disk 8, 0 2010-10-11 10:23 /dev/sda
brw-rw---- 1 root disk 8, 1 2010-10-11 10:23 /dev/sda1
brw-rw---- 1 root disk 8, 2 2010-10-11 10:23 /dev/sda2
brw-rw---- 1 root disk 8, 5 2010-10-11 10:23 /dev/sda5


शब्द "डिस्क" और तारीख के बीच दो अल्पविराम से अलग संख्याएँ हैं। पहली संख्या को प्रमुख उपकरण संख्या कहा जाता है। प्रमुख संख्या इंगित करती है कि इस डिवाइस को सेवा देने के लिए किस ड्राइवर का उपयोग किया जाता है। प्रत्येक ड्राइवर की अपनी विशिष्ट बड़ी संख्या होती है।

डिवाइस फ़ाइलों को mknod कमांड का उपयोग करके बनाया जाता है, उदाहरण के लिए: mknod /dev/test c 12 । इस आदेश के साथ, हम डिवाइस / देव / परीक्षण बनाएंगे और इसके लिए प्रमुख संख्या (12) का संकेत देंगे।

मैं सिद्धांत में गहरे नहीं जाऊंगा, क्योंकि जो रुचि रखते हैं - वह इसके बारे में अधिक विस्तार से पढ़ सकेंगे। मैं अंत में लिंक दूंगा।

शुरू करने से पहले



आपको कुछ "जादू" आज्ञाओं को जानने की आवश्यकता है:


मॉड्यूल को संकलित करने के लिए, हमें वर्तमान कर्नेल के हेडर की आवश्यकता है।

डेबियन / ubutnu में उन्हें आसानी से इस तरह रखा जा सकता है (उदाहरण के लिए, 2.6.26-2-686 के लिए):
apt-get install linux-headers-2.6.26-2-686
या अपने वर्तमान कर्नेल के लिए पैकेज का निर्माण स्वयं करें: fakeroot make-kpkg kernel_headers

स्रोत कोड



#include <linux/kernel.h> /* printk() .. */
#include <linux/module.h> /* , */
#include <linux/init.h> /* */
#include <linux/fs.h>
#include <asm/uaccess.h> /* put_user */

// , Modinfo
MODULE_LICENSE( "GPL" );
MODULE_AUTHOR( "Alex Petrov <petroff.alex@gmail.com>" );
MODULE_DESCRIPTION( "My nice module" );
MODULE_SUPPORTED_DEVICE( "test" ); /* /dev/testdevice */

#define SUCCESS 0
#define DEVICE_NAME "test" /* */

//
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, .
static int major_number; /* */
static int is_device_open = 0; /* ? */
static char text[ 5 ] = "test\n" ; /* , */
static char * text_ptr = text; /* */

//
static struct file_operations fops =
{
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};

// . . main()
static int __init test_init( void )
{
printk( KERN_ALERT "TEST driver loaded!\n" );

//
major_number = register_chrdev( 0, DEVICE_NAME, &fops );

if ( major_number < 0 )
{
printk( "Registering the character device failed with %d\n" , major_number );
return major_number;
}

//
printk( "Test module is loaded!\n" );

printk( "Please, create a dev file with 'mknod /dev/test c %d 0'.\n" , major_number );

return SUCCESS;
}

//
static void __exit test_exit( void )
{
//
unregister_chrdev( major_number, DEVICE_NAME );

printk( KERN_ALERT "Test module is unloaded!\n" );
}

//
module_init( test_init );
module_exit( test_exit );

static int device_open( struct inode *inode, struct file *file )
{
text_ptr = text;

if ( is_device_open )
return -EBUSY;

is_device_open++;

return SUCCESS;
}

static int device_release( struct inode *inode, struct file *file )
{
is_device_open--;
return SUCCESS;
}

static ssize_t

device_write( struct file *filp, const char *buff, size_t len, loff_t * off )
{
printk( "Sorry, this operation isn't supported.\n" );
return -EINVAL;
}

static ssize_t device_read( struct file *filp, /* include/linux/fs.h */
char *buffer, /* buffer */
size_t length, /* buffer length */
loff_t * offset )
{
int byte_read = 0;

if ( *text_ptr == 0 )
return 0;

while ( length && *text_ptr )
{
put_user( *( text_ptr++ ), buffer++ );
length--;
byte_read++;
}

return byte_read;
}

* This source code was highlighted with Source Code Highlighter .


मॉड्यूल विधानसभा



खैर, अब हम एक छोटा मेकफाइल लिख सकते हैं:

obj-m += test.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean


और इसके प्रदर्शन की जाँच करें:

रूट @ जोकर: / tmp / परीक्षण # बनाओ
make -C /lib/modules/2.6.26-2-openvz-amd64/build M=/tmp/test modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.26-2-openvz-amd64'
CC [M] /tmp/1/test.o
Building modules, stage 2.
MODPOST 1 modules
CC /tmp/test/test.mod.o
LD [M] /tmp/test/test.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.26-2-openvz-amd64'



आइए देखें कि हमें क्या मिला:

रूट @ जोकर: / tmp / परीक्षण # ls -la
drwxr-xr-x 3 root root 4096 21 12:32 .
drwxrwxrwt 12 root root 4096 21 12:33 ..
-rw-r--r-- 1 root root 219 21 12:30 demo.sh
-rw-r--r-- 1 root root 161 21 12:30 Makefile
-rw-r--r-- 1 root root 22 21 12:32 modules.order
-rw-r--r-- 1 root root 0 21 12:32 Module.symvers
-rw-r--r-- 1 root root 2940 21 12:30 test.c
-rw-r--r-- 1 root root 10364 21 12:32 test.ko
-rw-r--r-- 1 root root 104 21 12:32 .test.ko.cmd
-rw-r--r-- 1 root root 717 21 12:32 test.mod.c
-rw-r--r-- 1 root root 6832 21 12:32 test.mod.o
-rw-r--r-- 1 root root 12867 21 12:32 .test.mod.o.cmd
-rw-r--r-- 1 root root 4424 21 12:32 test.o
-rw-r--r-- 1 root root 14361 21 12:32 .test.o.cmd
drwxr-xr-x 2 root root 4096 21 12:32 .tmp_versions



अब आइए देखते हैं कि संकलित मॉड्यूल के बारे में जानकारी:

रूट @ जोकर: / tmp / test # modinfo test.ko
filename: test.ko
description: My nice module
author: Alex Petrov <druid@joker.botik.ru>
license: GPL
depends:
vermagic: 2.6.26-2-openvz-amd64 SMP mod_unload modversions


अंत में, कर्नेल में मॉड्यूल स्थापित करें:

रूट @ जोकर: / tmp / परीक्षण # insmod test.ko

आइए देखें कि क्या सूची के साथ हमारा मॉड्यूल है:

रूट @ जोकर: / tmp / परीक्षण # lsmod | grep परीक्षण

test 6920 0


और लॉग में क्या मिला:

रूट @ जोकर: / tmp / परीक्षण # dmesg | पूंछ

[829528.598922] Test module is loaded!
[829528.598926] Please, create a dev file with 'mknod /dev/test c 249 0'.


हमारा मॉड्यूल हमें बताता है कि क्या करने की आवश्यकता है।

उनकी सलाह का पालन करें:

रूट @ जोकर: / tmp / परीक्षण # mknod / dev / परीक्षण c 249 0

ठीक है, अंत में, आइए देखें कि हमारा मॉड्यूल काम करता है:

रूट @ जोकर: / tmp / परीक्षण # बिल्ली / देव / परीक्षण

test

हमारा मॉड्यूल उपयोगकर्ता से डेटा प्राप्त करने का समर्थन नहीं करता है:

रूट @ जोकर: / tmp / परीक्षण # इको 1> / देव / परीक्षण

bash: echo: :

आइए देखें कि मॉड्यूल हमारे कार्यों पर क्या कहता है:

रूट @ जोकर: / tmp / परीक्षण # dmesg | पूंछ

[829528.598922] Test module is loaded!
[829528.598926] Please, create a dev file with 'mknod /dev/test c 249 0'.
[829747.462715] Sorry, this operation isn't supported.


इसे हटाएं:

रूट @ जोकर: / tmp / परीक्षण # rmmod परीक्षण

और देखते हैं कि वह हमें क्या कहता है:

रूट @ जोकर: / tmp / परीक्षण # dmesg | पूंछ

[829528.598922] Test module is loaded!
[829528.598926] Please, create a dev file with 'mknod /dev/test c 249 0'.
[829747.462715] Sorry, this operation isn't supported.
[829893.681197] Test module is unloaded!


डिवाइस फ़ाइल हटाएं ताकि वह हमें परेशान न करे:

रूट @ जोकर: / tmp / परीक्षण # rm / dev / परीक्षण

निष्कर्ष



इस "वर्कपीस" का आगे विकास आपके ऊपर है। आप इसे एक वास्तविक ड्राइवर में बदल सकते हैं जो आपके डिवाइस को एक इंटरफ़ेस प्रदान करेगा, या लिनक्स कर्नेल के आगे के अध्ययन के लिए इसका उपयोग करेगा।

बस एक डिवाइस फ़ाइल के माध्यम से सुडोल बनाने के लिए एक पूरी तरह से पागल विचार के साथ आया था। यानी / dev / परीक्षण को एक कमांड भेजें और यह रूट के रूप में चलता है।

साहित्य



और अंत में मैं LKMPG (लिनक्स कर्नेल मॉड्यूल प्रोग्रामिंग गाइड) वर्तनी पुस्तक का लिंक दूंगा

युपीडी:
कुछ में ऊपर वर्णित मेकफाइल के माध्यम से निर्मित मॉड्यूल नहीं हो सकता है।
समाधान:
केवल एक पंक्ति के साथ एक मेकफिल बनाएँ: obj-m + = test.o
और विधानसभा को इस तरह से चलाएं:
मेक-सी / usr / src / linux-headers -`uname -r` SUBDIRS = $ PWD मॉड्यूल

UPD2:
स्रोत में त्रुटियों को ठीक किया।
पार्सर छोटी गाड़ी है और 'MODULE_DEscription ("मेरा अच्छा मॉड्यूल") बचाता है। स्वाभाविक रूप से मॉड्यूल_ शेड्यूल में सभी अक्षर अपरकेस हैं।

UPD3:
segoon ने पोस्ट में कुछ सुधार भेजे:

1) device_open () फ़ंक्शन में, एक दौड़ की स्थिति है:

स्थिर int device_open (स्ट्रक्चर इनोड * इनोड, स्ट्रक्चर फाइल * फाइल)
{
text_ptr = पाठ;

अगर (is_device_open) <<<<
वापसी -EBUSY;

is_device_open ++; <<<<

वापसी की सफलता;
}

यदि एक प्रक्रिया बढ़ती है तो दूसरे द्वारा रनटाइम पर is_device_open
यदि (is_device_open) और is_device_open ++ के बीच कमांड प्रक्रिया है, तो में
परिणामस्वरूप, फ़ाइल 2 बार खुल जाएगी। परमाणु क्रियाओं के लिए आपको उपयोग करने की आवश्यकता है
atomic_XXX () श्रृंखला से कार्य करता है।

डेटा के साथ सभी स्थानों पर परमाणु संचालन का उपयोग किया जाना चाहिए। इस मामले में, और पास में ()।

2) device_write () बिल्कुल नहीं लिखा जा सकता है, क्योंकि के लिए हैंडलर
डिफ़ॉल्ट रूप से, लिखना () स्वयं एक त्रुटि देता है।

3) put_user () के लिए, परिणाम की जांच करना MANDATORY है। यदि शून्य नहीं है, तो
या तो जरूरत है
a) परिणाम लौटाएं -फल करें और बहाना करें कि कुछ भी नहीं था (अर्थात
इसमें आंतरिक बफ़र्स के अधूरे पढ़े गए डेटा को न हटाएं
यदि मामला स्थिर है और कुछ भी बदलने की जरूरत नहीं है)
ख) लिखित बाइट्स की संख्या वापस लौटाएं (इसे आंशिक रीड कहा जाता है,
POSIX द्वारा अनुमति दी गई है)। इस स्थिति में, आपको यह सुनिश्चित करना होगा कि आप 0 न लौटें:
read () = 0 का मतलब है कि फाइल समाप्त हो गई है, लेकिन ऐसा नहीं है।

4) कर्नेल में, 0, नहीं
कॉन्स्टेंट एसयूसीसीस है। उदाहरण के लिए, अपवाद हैं
नेटवर्क पैकेट हैंडलर, लेकिन जहां -EXXX को वापस लौटाया जाता है (कोड)
त्रुटियाँ), या 0 (सब कुछ ठीक है), यह स्थिर 0 है जिसका उपयोग किया जाता है।

कई और कार्यों को अधिक उपयुक्त एनालॉग के साथ बदला जा सकता है, लेकिन यह
शुरुआती द्वारा लेख की समझ को जटिल करेगा :)

Source: https://habr.com/ru/post/In106702/


All Articles