ورود/ایجاد حساب کاربری
   منوی اصلی
· خانه
· لیست کاربران
· جستجو
· آمار مشاهدات
· آرشیو مقالات


- شرح
· راهنمای نویسندگان
· درباره ما

   همکاری با نشریه
در صورتی که مایل به همکاری با نشریه هستید، می‌توانید در لیست پستی نشریه عضو شده و در جریان امور قرار گیرید. برای اطلاعات بیشتر، اینجا کلیک کنید.

   کاربران
سردبیر
هیچ مدیر کمکی حاضر
همکاران
هیچ مدیر کمکی حاضر
اعضا:
جدیدترین:جدید امروز:0
جدیدترین:جدید دیروز:0
جدیدترین:مجموع:2471
جدیدترین:جدیدترین:
ufumenarayu
اعضا:حاضر
اعضا:اعضا:0
مهمان‌ها:مهمان‌ها:4
مجموع:مجموع:4
کاربران حاضر
هیچ کاربر حاضری وجود ندارد

   ورود کاربران




 


 برای ورود مشکل دارید؟
 ثبت نام کاربران جدید

ماژول نویسی برای هسته لینوکس (قسمت چهارم)

(2028 مجموع کلمات موجود در متن)
(8562 بار مطالعه شده است)  نسخه چاپی

ماژول نویسی برای هسته لینوکس (قسمت چهارم)


در قسمت‌های قبل با مفاهیم اولیه ماژول‌های هسته اشنا شدیم و به عنوان مثال چند ماژول بسیار ساده نوشتیم و ان ها را در هسته load کـــردیم. در‌این قسمت به بسط مفاهیم قبلی خواهیم پرداخت و بـــا Device Driver ها به عنوان دسته مهمی‌از ماژول‌های هسته لینوکس اشنا خواهیم شد.


ماژول ها چگونه اغاز و پایان می‌یابند؟

برنامه‌های معمولی , معمولا با تابعی به نام ()main اغاز شده , لیستی از دستورات را انجام داده و به پایان می‌رسند. ماژول‌های هسته در‌این مورد متفاوت عمل می‌کنند. یک ماژول همیشه با تابع ()init_module و یا تابعی که به وسیله ماکروی ()module_init به عنـــوان ورودی ثبت شده اغاز می‌گردد. در حقیقت‌این تابع قابــلیت خود را به هسته اعلام می‌دارد و به هسته‌این امــکان را داده که در موقع نیاز از توابع ماژول استفاده کند. پس از‌اینکه‌این تابع به پایان می‌رسد توابع ماژول دیگر اجرا نخواهند شد تا زمانی که هسته بخواهد از‌این توابع استفاده کند.

تمامی‌ماژول ها با صدا کردن cleanup_module() و یا تابعی که به وسیله ماکروی module_exit() به عنوان ورودی ثبت شده به پایان می‌رسند.‌این تابع تمامی‌اعمالی را که تابع ورودی انجام داده خنثی می‌کند.


توابعی که در اختیار ماژول ها هستند

توسعه دهندگان معمولا از توابعی استفاده می‌کنند که خود انها را تعریف نکرده اند. یک مثال ساده از‌این توابع , تابع ()printf می‌باشد. شما از‌این توابع که در کتابخانه استاندارد زبان C تعریف شده اند استفاده می‌کنید. کد‌این توابع تا زمان لینک یا پیوند در کد شما وارد نمی‌شوند و در زمـــان لینک ادرس موردنظر در کد شما به آدرس‌ این کد اشاره داده خواهد شد.

ماژول‌های هسته در‌این مورد نیز متفاوت هستند. شما اگر در مثال‌های قسمت قبل دقت کرده باشید ما از تابعی به نام ()printk استفاده کردیم امـــا از کتابخانه استانداردی بــرای IO استفاده نکردیم.‌ ایــن مــوضــوع بــه‌ ایـن خـاطر است که ماژول ها فایل‌های object‌ای هستند که سمبول‌هایشان در هنــــگــام insmod مشخص می‌گردند. کد‌این سمبول ها در خود هسته وجود دارد. اگــر می‌خواهیـــد سمبــــولهایی را کـــه هسته تعــــریف کـــرده اســت را مشاهده کنید به فایل proc/kallsyms/ رجوع کنید.

یکــی از نـکات بسیار مهمی‌که باید مـــورد توجه قرار گیـــرد تفـــاوت بین توابع کتابخانه‌ای ( library functions ) و توابع سیستمی‌( system calls ) است. تــوابـــع کتــابخانه‌ای توابعی سطح بالا هستند که در فضای کاربر اجرا می‌شوند و در حقیقت واسط بین کاربر و توابع سیستمی‌که اصل کار هر برنامه را می‌کنند می‌باشند. توابع سیستمی‌در فضای هسته اجرا می‌شوند. تابع کتابخانه‌ای ()printf یک تابع نمایش بسیار عمومی‌به نظر می‌اید اما در حقیقت‌این تابع رشته ورودی را شکل دهی کرده و ان را تحویل تابع سیستمی‌()write ( که کار حقیقی را انجام می‌دهد ) می‌دهد.

برای‌اینکه از جزییات عملکرد ()printf با خبر شوید کد زیر را در یک فایل به نام hello.c بنویسید.

#include <stdio.h>

int main() { printf(“hello”); return 0; }

و با دستور زیر ان را به فایل اجرایی hello تبدیل کنید:

$ gcc -Wall -o hello hello.c

حال در‌این مرحله hello را به صورت زیر اجرا کنید:

$ strace ./hello

چیزی که مشاهده خواهید کرد مجموعه توابع سیستمی‌است که برنامه hello صدا زده است. در چند خط اخر خروجی دستور قبل , خطی به صورت زیر خواهید دید :

write( 1 , ”hello” , 5hello )

این خط در حقیقت خطی است که اصل عملکرد ()printf اجرا شده است. برای اگاهی بیشتر از تابع سیستمی‌()write دستور man 2 write را اجرا کنید.

شما حتی می‌توانید ماژول‌هایی بنویسید که توابع سیستمی‌هسته را تغییر دهد. cracker ها معمولا از‌این خاصیت برای نوشتن backdoor یا trojan ها استفاده می‌کنند.

فضای کاربر در مقابل فضای هسته

هسته به تمام منابع سیستم دسترسی مستقیم دارد.‌این منابع می‌توانند کارت ویدیو , دیسک سخت یا حافظه باشند. در حالی که برنامه‌های معمولی، بــر سر تصاحب منابع سیستم رقابت دارند. هم اکنون که من درحال نوشتن‌این متن هستم، updatedb در حال به روز رسانی پایگاه داده locate می‌باشد، logger ها در حـــال ثبـــت وقـایع هستند. بنابراین برنامه‌های syslogd ،updatedb ،openoffice.org بـــه طــــور همــزمان از هارد دیسک استفاده می‌کنند. واضح است که هسته بایستی ترتیب استفاده را مشخص کند و به برنامه‌ها و کاربران اجازه دسترسی بـــه منـــابع هـر زمان که دوست دارند ندهد.

بــرای رسیدن به‌این هدف یک CPU در حالـــت‌های مختلفی می‌تواند بــــه اجرای دستورات بپردازد. هــر حــــالتی سطح مختلــفــی از آزادی بـــرای کســـب منــابــع سیستم در اختیار کاربران قرار می‌دهد. معـماری Intel 80386 چهار حالت یا اصطلاحا mode دارد. لینوکس تنها از دو حالت استفاده می‌کند:

۱) بالاترین سطح آزادی یا حالت سرپرست و مدیر: که در‌ ایــن حالت همه چیز امکان پذیر است و به همه منابع سیستم می‌توان دسترسی مستقیم داشت.

۲) پایین ترین سطح آزادی یا حالت کاربر: که در‌این حالت استفـــاده از منابع سیستم تنـــها بـــا اجازه هسته امکان پذیر است.

بحث قبلی در مورد توابع کتابخانه‌ای و توابع سیستمی‌را به خاطر آورید. تــوابع کتابخانه‌ای اغلب در حالت کاربر استفاده می‌شوند.‌این توابع نیز یک یا چند تابع سیستمی‌را صدا می‌زنند.‌ ایـن توابع سیستمی‌با‌این که از طرف توابع کتابخانه‌ای صـــدا زده می‌شوند در فضای هسته اجرا می‌شــوند بدلیل‌ اینــــکه‌ ایـن توابع بخشی از هسته هستند. هنــگــامی‌ کــه تابع سیستمی‌به طور کامل اجرا شد حالت اجرای دستورات به حالت کاربر بر می‌گردد.


فضای متغییرها (Name Space)

هنگامی‌ کــه شما یک برنامه به زبان C می‌نویسید از اسامی‌راحتی به عنوان نام متغیرهایتان استفاده می‌کنید و با‌ این کار خود سعی در هرچه بیشتر خوانا تر کردن کد خود می‌کنید. اگــر شــما روتین‌هایی بنویسید که بخشی از یک مساله بــزرگتر باشند، هر متغیر عمومی‌یا global که استفاده می‌کنید جزیی از مجموعه متغیرهای عمومی‌دیگران خـــواهد بود. بنابراین مواردی پیش خواهد امد که متغیر‌های عمومی‌در دو کد مجزا یکسان باشند و کار دچار مشکل شود.

هنگامی‌ کـــه یک برنامه دارای متغیرهای عمومی‌بسیاری می‌باشد کـــه بـــه اندازه کافی معنادار نیستند به‌این مساله آلودگی فضای متغیرها یا name space pollution گویند.

در پروژه‌های بزرگ بایستی سعی شود که روش‌هایی برای‌ایجاد نام‌های متغیرها‌ ایجاد شوند که نام متغیرها هــم یکتا باشند و هم دارای معنی مناسب باشند. یکی از همین پروژه‌های بسیار بزرگ هسته لینوکس است. هنــگام نوشتن کد هسته، حتـــی کوچکترین ماژول نیز با کل هسته پیوند ( link ) خواهد شد. بــنابراین‌این موضوع از اهمیت بسیار بالایی برخوردار است. بهترین راه برای حل‌این مساله تعریف کردن متغیرهای عمومی‌ بــه صورت static و یا‌ استفاده از پیشوند یــا پسوند‌های مناسب برای نام گذاری است.معمولا تمام پیشوند‌هایی که در هسته تعریف می‌شوند با حروف کوچک اغاز می‌شوند.

اگـــر شما نمی‌خواهید که متغیرهایتان را به صورت static تعریف کنید گزینه دیگری که پیش روی شماست تعریف یک جدول سمبول ها ( symbol table ) و ثبت آن در هسته است. بعدا به‌این مقوله بیشتر خواهیم پرداخت.

فایل proc/kallsyms/ تمامی‌ سمبول‌هایی که در هسته تعریف شده‌اند و شما می‌توانید از آنـــها برای مــــاژول‌های خود استفاده کنید را نگهداری می‌کند.


فضای کد (Code Space)

مدیریت حافظه یکی از پیچیده ترین و با اهمیت ترین مــوضـــوعات در هسـته است (موضوعی که بیشتر کتاب OReilly’s Understanding the Linux Kernel در‌این باره می‌باشد). مـــا در‌این راهنما قصد نداریم کـــه در زمیـــنه مدیـــریـت حافظه حـــرفه‌ای شویم. اما نیاز داریم که حقایق بسیار مهمی‌را بدانیم تا بتوانیم مـــاژول‌های واقـــعی بـــرای هــستـه لینوکس بنویسیم.

اگـــر شمـــا چـیزی در مورد segfault ها نمی‌دانید ممکن است متعجب شوید که اشاره گر ها (pointers) که در برنامه نویسی به خصوص با زبان C به کار می‌روند واقعا به آدرسی از حافظه اشاره نمی‌کنند.

هنگامی‌که یک پروسه ایجاد می‌شود، هسته قسمتی از حافظه فیزیکی را گرفته و در اختیار پروسه قــرار می‌دهد که برای اجرای کد، نگهداری متغیرها، heap ،stack و تمام چیزهایی که یک پروسه نیاز دارد از ان استفاده کند.‌این فضا برای تمام پروسه‌ها از آدرس $0$ شروع شده و به میزان خطوط آدرس دهـــی ( Address Bus ) حـــافظه (32^2 بایت) قـــابل گسترش است. از آنجایی کــه پروسه‌ها بــر روی یکــدیگر قرار نمی‌گیرند، بنـــابراین هر چند پروسه کـــه بــه یــک آدرس دسترسی دارند ( مثلا 0xbfffff978 ) در حقیقت به آدرس‌های متفاوتی از حافظه فیزیکی دسترسی دارند!‌ ایـــن مطــلب دقیقا همان است که اشاره گرها به آدرسی از حافظه فیزیکی اشاره نمی‌کنند.

در حقیقت در تمام پروسه‌هایی که دارای اشاره گری با مقدار 0xbfffff978 هستند‌، این اشاره گر به نوعی از offset که فقط در آن پروسه تعریف شده است اشاره می‌کند. هیــچ وقت دو پروسه نمی‌توانند بـــه فضای یکدیگر وارد شوند ( البته راه‌هایی وجود دارد که بعدا ذکر خواهیم کرد).

خود هسته نیز فضایی از حافظه را برای خود در اختیار دارد. یک ماژول کدی است که به صورت دینامیک می‌تواند وارد‌ این فضا از حافظه شده یا از ان خارج شود. توجه به‌این نکته بسیار حیاتی است که هر ماژول سهمی‌از فضای هسته را استفاده می‌کند و فضای جدیدی برای آن گرفته نمی‌شود. بنابراین اگر ماژول شما دچار segmentation fault شود، کل هسته دچار segmentation fault خواهد شد.

نکته دیگر‌این است که‌این بحث برای تمام سیستم‌هایی که به صورت microkernel هستند درست است. دو نمونه از‌این سیستم عامل ها GNU Hurd و QNX Nutrino هستند.


راه انداز ها (Device Drivers)

یکی از انواع ماژول ها راه اندازهای قطعات سخت افزاری هستند کـــه روتین‌های لازم بــرای استــفـاده از قطعات سخت افزاری مانند کارت TV یا پورت سریال و.... را در اختیار قرار می‌دهند.

در Unix هر قطعه سخت افزاری با یک فایل که در dev/ قرار می‌گیرد مشخص می‌گـــردد. به‌این فایل، فــایـــل دســتگاه (device file) گویند که برای برقراری ارتباط برنامه‌های مختلف با قطعه سخت افزاری مورد نظر می‌باشد.

راه انـــدازی که به‌این فایل مربوط می‌شود، در مقابل ارتباط برنامه‌های کاربران در مـــورد‌ ایـن قطعه سخت افزاری پاسخ می‌دهد. بنا براین راه انداز کارت صدایی مانند es1370.o فایل دستگاه dev/sound/ را به کارت صدای Ensoniq IS1370 متصل می‌کند. یک برنامه مانند mp3blaster می‌تواند از dev/sound/ بدون‌اینکه حتی بداند چه کارت صدایی نصب شده است استفاده کند.


اعداد اصلی ( major ) و فرعی ( minor )

بیایید چند فایل device را مورد بررسی قرار دهیم. در مثال زیر 3 پارتیشن اول هارددیسک master نشان داده شده اند:

$ ls -l /dev/had[1-3]
brw-rw---- 1 root disk 3, 1 Jul 5 2000 /dev/hda1
brw-rw---- 1 root disk 3, 2 Jul 5 2000 /dev/hda2
brw-rw---- 1 root disk 3, 3 Jul 5 2000 /dev/hda3

بــه ستـــونی که با کاما از هم جـــدا شده‌اند تـــوجه کنید. اولیــن عدد در‌این ستون عدد اصلی دستگاه یا device major number نامیده می‌شود. دومین عدد در‌این ستون عدد فرعی دستگاه یا device minor number می‌باشد. عدد اصلی به شما می‌گوید که چه راه اندازی برای‌این دستگاه مورد استفاده قرار گرفته است. به هر راه اندازی یک عدد یکتا نسبت داده شده است. تمام دستگاه‌های با عدد اصلی یکسان توسط یک راه انداز کنترل می‌شوند. در مثال بالا عدد اصلی هر سه دستگاه 3 می‌باشد که نشان می‌دهد که هر سه توسط یک راه انداز کنترل می‌شوند.

عدد فرعی توسط خود راه انداز برای تمایز بین دستگاه‌هایی که تحت کنترل دارد استفاده می‌شود. در مثال بالا با‌اینکه هر سه دستگاه توسط یک راه انداز کنترل می‌شوند ولی عددهای فرعی متفاوتی ( و البته یکتا ) دارند چون که از دید راه انداز ان ها سه دستگاه متفاوت هستند.

دستگاه ها به دو دسته تقسیم می‌شوند:

۱) دستگاه‌های کاراکتری ( character devices )

۲) دستگاه‌های بلوکی ( block devices )

تفاوت بین‌ این دو دسته در‌ این است که دستگاه‌های بــلــوکی برای انجام تقاضاهای مختلف از بافر ( buffer ) استفاده می‌کنند. در دستگاه‌های ذخیره سازی اطلاعات خواندن یا نوشتن اطلاعات به صورت مجموعه‌ای ( بلوک یا سکتور ) از اهمیت بالایی برخوردار است. تفاوت دیگر بین‌این دو نوع دستگاه‌این است که دستگاه‌های بلوکی فقط به صورت بلوکی از داده ها ( که می‌تواند اندازه متغیری داشته باشد ) ورودی دریافت می‌کنند و خــروجــی بـــر می‌گـــردانند در حالی که دستگاه‌های کاراکتری به هر تعداد بایت می‌توانند ورودی دریافت کنند و خروجی برگردانند.

بیشتر دستگاه ها از نوع کارکتری هستند به دلیل‌اینکه در اکثر موارد نیازی به‌این نوع بافر وجود ندارد و انها اغلب با یک بلوک ثابتی از داده ها کار نمی‌کنند. برای فهمیدن‌اینکه کدام دستگاه به صورت بلوکی و کدام به صورت کاراکتری عمل می‌کنند اولین حرف از خروجی دستور ls –l نـــوع دستـــگاه را مشخص می‌کند. اگـــر c بــود کاراکتری و اگر b بود بلوکی می‌باشد. در مثال سه پارتیشن هارد دیسک همگی از نوع بلوکی هستند.

مثالی از دستگاه‌های کاراکتری پورت سریال می‌باشد :

$ls -l /dev/ttyS[0-3]
crw-rw---- 1 root dial 4, 64 Feb18 23:34 /dev/ttyS0
crw-rw---- 1 root dial 4, 65 Nov17 10:26 /dev/ttyS1
crw-rw---- 1 root dial 4, 66 Jul 5 2000 /dev/ttyS2
crw-rw---- 1 root dial 4, 67 Jul 5 2000 /dev/ttyS3

اگر می‌خواهید بدانید که چه اعداد اصلی در حال حاضر ثبت شده هستند به فایل زیر در کد منبع هسته لینوکس مراجعه کنید :

linux/Documentation/devices.txt

هنگامی‌که سیستم را نصب می‌کنید تمام ان فایل‌های دستگاه ها با دستور mknod‌ایجاد می‌شوند.

برای‌ایجاد یک فایل دستگاه جدید از نوع کاراکتری به نام coffee با اعداد اصلی و فرعی 12 و 2 به صورت زیر می‌توان عمل کرد :

# mknod /dev/coffee c 12 2

معمـولا فایل‌های دستگاه ها در dev/ قرار می‌گیرد. لینوس توروالدز فایل‌های دستگاه‌هایش را برای اولین بار در dev/ قرار داد و‌ ایــن کــــار تقریبا مرسوم شده است. با‌ این حال اگـــر برای تست ماژول هسته‌ای کــــه نوشته‌اید می‌خواهید فایل دستگاهی‌ ایجاد کنید، بهتر است که آن را در دایرکتوری جاری قرار دهید.

به عنوان اخرین نکته، هنـــگامی‌ کـه ما می‌گوییم سخت افزار منظورمان کمی‌ متفاوت نسبت به یک قطعه سخت افزاری مثلا PCI Card است که شما می‌توانید در دست خود بگیرید.

به مثال زیر توجه کنید :

$ ls -l /dev/fd0 /dev/fd0u1680
brwxrwxrwx 1 root floppy 2, 0 Jul 5 2000 /dev/fd0
brw-rw---- 1 root floppy 2, 44 Jul 5 2000 /dev/fd0u1680

با توجه به چیزی که تاکنون گفتیم شما به راحتی می‌توانید بگویید که هر دو دستگاه بالا بلوکی هستند و توسط یـک راه انداز کنترل می‌شوند. شما ممکن است متوجه شده باشید که هر دو درایو فلاپی شما را نشان می‌دهند. در صورتی که شما فقط یک دستگاه فلاپی در سیستم تان دارید. پس چرا 2 فایل دستگاه برای آن‌ ایجاد شده است ؟ جواب‌ ایـن سوال در حقیقت پاسخ مساله‌ای است که در بالا اشاره شد.

اولین فایل فلاپی شما با 1.44MB حافظه را مشخص می‌کند. دومین فایل همان فـــلاپی است با‌ ایـــن تفاوت که توانایی خواندن و نوشتن فـــلاپی‌های بـــا حـــافــظه 1.68MB ( که به آنها super formatted می‌گویند) را دارد. بنابراین مشاهده می‌کنید که دو فایل دستگاه یک دستگاه را مشخص می‌کند. بنابراین در بحث مان بیشتر به معنی دستگاه و سخت افزار توجه داشته باشید. در‌ اینجا‌این قسمت به پایان می‌رسد. در قسمت‌ آینده در مورد درایور‌های دستگاه‌های کاراکتری که از اهمیت ویژه‌ای در ماژول نویسی هسته لینوکس برخوردارند بحث خواهیم کرد.



ترجمه و تکمیل : سعید تقویs.taghavi@ece.ut.ac.ir

PDF Version

منابع :

1) http://www.tldp.org/LDP/lkmpg/2.6/html

2) http://www.linuxhq.com/guides/LKMPG/mpg.html

تمامی مطالب و مقالات این سایت تحت مجوز GNU FDL قرار دارند. بنابراین کپی و ایجاد تغییر در آنها مطابق شرایط این مجوز آزاد می‌باشد.