ماژول نویسی برای هسته لینوکس (قسمت چهارم)(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 |