ماژول نویسی برای کرنل قسمت ۱۳(1884 مجموع کلمات موجود در متن) (7367 بار مطالعه شده است) ماژول
نویسی برای هسته لینوکس (قسمت
سیزدهم)
در
این قسمت به بررسی دو موضوع نسبتا جدا از
هم در ماژول نویسی هسته لینوکس خواهیم
پرداخت.
در
بخش اول با عنوان توابع سیستمی با نحوه
نوشتن و تغییر توابع سیستمی هسته لینوکس
اشنا خواهیم شد و در بخش دوم با عنوان
متوقف کردن پروسه ها نحوه مدیریت پروسه
های در حال اجرا و پروسه های در حال انتظار
را فرا خواهیم گرفت.
توابع
سیستمی (
System Calls )
انچه
تاکنون در ماژول نویسی هسته لینوکس انجام
دادیم استفاده از مکانیزم های دقیق هسته
مانند ثبت فایل در proc/
و
راه انداز فایل ها و ...
بود.
همه
چیز مرتب است اگر شما بخواهید کاری انجام
دهید که توسعه دهندگان هسته ان را پیش
بینی کرده و مکانیزمی در هسته برای کار
شما در هسته ایجاد کرده باشند مانند نوشتن
راه انداز دستگاه.
اما
اگر شما بخواهید کار غیر معمولی انجام
دهید که توسعه دهندگان هسته راه کاری برای
ان ایجاد نکرده اند چطور ؟ در اغلب این
موارد مسئولیت همه چیز با خودتان است.
اینجا
جایی است که برنامه سازی هسته خطرناک می
شود.
با
نوشتن ماژول هسته مثال شما یکی از این
کارهای خطرناک را انجام خواهید داد.
کد
این مثال را از اینجا
[۱]می
توانید بدست اورید.
در
این مثال شما تابع سیستمی open
را
تعویض خواهید نمود.
این
بدان معنی است که هیچ کس در سیستم دیگر
نمی تواند فایلی را باز کند (
هیچ
کس نمی تواند برنامه ای اجرا کند و حتی
کامچیوتر را خاموش نماید ).
تنها
راه راه اندازی سخت افزاری سیستم می باشد.
خوشبختانه
هیچ فایلی از بین نخواهد رفت.
برای
حصول اطمینان از خراب نشدن فایل ها قبل
از هر insmod
و
هر rmmod
یک
بار دستور sync
را
اجرا کنید.
فایل
های proc/
و
فایل های دستگاه ها در dev/
را
فراموش کنید.
پروسه
اصلی در مکانیزم ارتباط با هسته (
که
توسط تمام پروسه ها استفاده می شود )
استفاده
از توابع سیستمی می باشد.
هنگامی
که یک پروسه سرویسی را از هسته درخواست
می نماید (
مانند
باز کردن یک فایل ,
ایجاد
یک پروسه جدید یا درخواست حافظه بیشتر )
از
این مکانیزم استفاده می شود.
اگر
شما می خواهید رفتار هسته را به دلخواه
خود تغییر دهید اینجا جایی است که می
توانید تغییرات خود را اعمال نمایید.
برای
دانستن توابع سیستمی که در برنامه ها به
کار می روند می توانید از دستور strace
به
صورت زیر استفاده نمایید.
$strace
programname
به
طور کلی یک پروسه نمی تواند به هسته دسترسی
داشته باشد.
بدان
معنی که نمی تواند به حافطه هسته دسترسی
داشته باشد و نمی تواند توابع هسته را صدا
نماید.
( این
سیستم ها protected
mode نامیده
می شوند)
. توابع
سیستمی استثنای این قاعده کلی هستند.
اتفاقی
که می افتد این است که پروسه رجیسترهای
پردازنده را با مقادیر مناسب پر کرده و
دستور خاصی را صدا کرده که باعث پرش به
محل از پیش تعیین شده ای از هسته می شود.
( که
البته ان محل از حافظه توسط پروسه ها قابل
خواندن است ولی قابل نوشتن نیست ).
در
پردازنده های اینتل این رویه با استفاده
از وقفه (
interrupt ) شماره
0x80
انجام
می گیرد.
CPU به
عنوان سخت افزار کامپیوتر می داند هنگامی
که شما به این نقطه پرش می کنید نمی خواهید
که در حالت محدود شده ادامه کار دهید و به
عنوان کدی از هسته سیستم عامل به شما اجازه
هر کاری که بخواهید را می دهد.
مکانی
در هسته که پروسه می تواند به ان پرش نماید
system_call
نامیده
می شود.
رویه
در این موقعیت از هسته به این صورت است که
شماره تابع سیستمی که بیانگر سرویسی است
که پروسه از هسته می خواهد چک شده و در
جدول توابع سیستمی (
sys_call_table ) تابع
موردنظر برای صدا زدن جستجو خواهد شد و
سپس تابع موردنظر صدا زده می شود و پس از
بازگشت تابع و انجام چندین چک از طرف سیستم
به پروسه بازگشت خواهد شد (
یا
اگر زمان پروسه تمام شده باشد به پروسه
دیگری ارجاع خواهد شد )
برای
مشاهده کد اسمبلی این رویه به ادرس Linux
Source Code/arch/<$architecture$>/kernel/entry.S از
کد هسته لینوکس بعد از خط (ENTRY(system_call
مراجعه
کنید.
بنابراین
اگر بخواهیم عملکرد تابع سیستمی ای را
تغییر دهیم کافیست تابع خودمان را بنویسیم
و اشاره گر به تابع اصلی در sys_call_table
را
با اشاره گر به تابع خودمان جایگزین نماییم
تا به تابع ما اشاره کند.
فقط
بایستی یادمان باشد که در تابع cleanup_module
تمام
تغییراتی که در این جدول اعمال کرده ایم
را به حالت اولیه بازگردانیم تا سیستم
در
حالت ناپایدار باقی نماند.
کد
مثالی که از اینجا [1]
می
توانید دریافت کنید کد یک ماژول کرنل می باشد. ما می خواهیم جاسوسی یکی از کاربران را انجام دهیم به صورتی که هرگاه کاربر موردنظر ما فایلی را باز کرد یک پیغام به وسیله ()printk در فایل log هسته چاپ کنیم. برای این کار تابع سیستمی ()open را با تابع خودمان به نام our_sys_open جایگزین می نماییم. این تابع user's id ) uid ) پروسه جاری را چک کرده و اگر برابر با uid کاربر موردنظر ما بود پیغام موردنظر ما را چاپ می کند و در نهایت فایل موردنظر کاربر را به وسیله تابع اصلی ()open باز
می نماید.
در تابع init_module اشاره گر موردنظر در جدول sys_call_table جایگزین شده و مقدار اصلی این اشاره گر در یک متغیر
ذخیره می شود. تابع cleanup_module از این متغیر استفاده کرده و همه چی را به
حالت عادی باز می گرداند. /P
این
روش , روشی
خطرناک است چون ممکن است که دو ماژول هسته
قصد تعویض یک تابع سیستمی را داشته باشند.
فرض
کنید که دو ماژول کرنل A
و
B
داشته
باشیم.
تابع
()open
جایگزین
شونده در ماژول A_open
, A و
در ماژول B_open
, B است.
ماژول
A
در
هسته وارد می شود بنابراین تابع سیستمی
()open
با
A_open
جایگزین
می شود.
حال
ماژول B
در
هسته وارد می شود که باعث جایگزینی A_open
با
B_open
می
شود.در
مورد خروج ماژول ها از هسته اگر ماژولB
و
سپس ماژول A
از
هسته خارج شوند همه چیز درست خواهد بود
اما اگر برعکس خارج شوند چطور ؟ خودتان
می توانید حدس بزنید که چه اتفاقی خواهد
افتاد .
اگر
A
ابتدا
از هسته خارج شود مقدار جاری اشاره گر به
تابع سیستمی که به B_open
اشاره
می کند را با open
اصلی
تعویض خواهد کرد و اگر در این لحظه B
از
هسته خارج شود مقدار جاری اشاره گر که به
open
اصلی
اشاره می کند را با مقذار ذخیره کرده خود
که همان A_open
است
جایگزین می نماید و چون A_open
در
هسته وجود ندارد سیستم دچار crash
خواهد
شد.
و
موارد زیاد دیگری که می تواند سیستم را
دچار crash
کند.
مسائلی
از این قبیل اجازه کار با توابع سیستمی
را برای کارهای تولیدی که به استفاده عموم
می رسد را غیر ممکن می سازد.
برای
جلوگیری از انجام کارهای خطرناک توسط
افراد ,
دیگر
متغیر sys_call_table
که
به جدول توابع سیستمی اشاره می کند در
فضای هسته قرار داده نشده است.
بنابراین
اگر شما می خواهید که ماژول مثال مورد بحث
را بتوانید در هسته وارد کنید بایستی هسته
جاری خود را patch
کرده
و دوباره کامپایل نمایید تا متغیر
sys_call_table
را
در حافظه هسته داشته باشید.
این
patch
را
می توانید در دایرکتوری مثال پیدا کنید.
( شاید
نیاز داشته باشید که برای نسخه هسته خود
کمی کد نیز به صورت دستی اعمال کنید )
متوقف
کردن پروسه ها (
Blocking Processes )
هنگامی
که کسی از شما چیزی می خواهد که شما نی
توانید انجام دهید چه می کنید ؟ اگر شما
یک انسان باشید و کسی که از شما درخواست
کرده نیز یک انسان باشد به او می گویید :
“الان
نه ,
سرم
شلوغه” .
اما
اگر شما یک ماژول هسته باشید که یک پروسه
از شما درخواستی کرده باشد امکان دیگری
نیز خواهید داشت .
شما
می توانید پروسه را به حالت خواب ببرید
تا زمانی که بتوانید به ان سرویس دهید .
ماژول
هسته کد مثال که از اینجا [2] میتوانید دریافت کنید مثالی از این کار است. در این مثال فایل proc/sleep/ توسط فقط یک پروسه در هر لحظه می تواند باز شود. اگر فایل در حال حاضر باز باشد ماژول هسته تابع wait_event_interruptible را صدا زده که باعث تغییر وضعیت کار ( task ) ( task ساختار داده ای در هسته است که اطلاعاتی در مورد پروسه و تابع سیستمی ای که پروسه در ان است نگه می دارد ) به حالت TASK_INTERRUPTIBLE می شود بدین معنا که کار تا زمانی که کسی ان پروسه را بیدار نکند ادامه نمی یابد و ان را به صفی به نام WaitQ که پروسه های منتظر دسترسی به فایل proc/sleep/ هستند اضافه می کند و به scheduler سیستم عامل دستور context switch را می دهد که CPU
را
به دست پروسه دیگری بدهد.
هنگامی
که یک پروسه کار خود را با فایل به اتمام
رساند ان را می بندد که باعث به صدا درامدن
تابع module_close
در
ماژول هسته می شود.
این
تابع تمام پروسه های موجود در صف WaitQ
را
بیدار می کند و پروسه ای که بتواند فایل
را تصاحب کند به کار خود ادامه می دهد.
این
پروسه کار خود را از تابع
module_interruptible_sleep_on
اغاز
کرده و متغیری عمومی را تنظیم می کند تا
به پروسه های دیگر نشان دهد که فایل هم
اکنون باز شده است.
پروسه
های دیگر به دیدن وضعیت این متغیر به حالت
خوابیده باز می گردند.
در
این مثال ما با استفاده از دستور tail
-f فایل
مورد نظرمان را در پس زمینه باز نگه می
داریم و با استفاده از فایل اجرایی
cat_nonblock
که
با استفاده از دستور زیر ایجاد می شود
فایل را دوباره باز می کنیم.
$gcc cat_nonblock.c -o
cat_non_block
اگر
با استفاده از دستور kill
%1 اولین
پروسه پس زمینه را بکشیم پروسه دوم که به
حالت خوابیده رفته بود به کار خود ادامه
داده و نهایتا پایان می یابد.
درباره
مثال قسمت دوم :
کد
مثال را از اینجا [2] میتوانید دریافت کنید. ماژول مثال را که در فایل sleep.c پیاده سازی شده است را با استفاده از دستور make کامپایل
کنید و کد cat_nonblock.c
را
همانطور که در بالا اشاره شد با دستور gcc
کامپایل
کنید.
debian:~/lkmpg13/blockproc#
insmod sleep.ko
debian:~/lkmpg13/blockproc#
cat_noblock /proc/sleep
Last
input:
debian:~/lkmpg13/blockproc#
tail −f /proc/sleep &
Last
input:
Last
input:
Last
input:
Last
input:
Last
input:
Last
input:
Last input:
tail:
/proc/sleep: file truncated
[1]
6540
debian:~/lkmpg13/blockproc#
cat_noblock /proc/sleep
Open
would block
debian:~/lkmpg13/blockproc#
kill %1
[1]+
Terminated tail −f /proc/sleep
debian:~/lkmpg13/blockproc#
cat_noblock /proc/sleep
Last
input:
debian:~/lkmpg13/blockproc#
جزییات
مثال در کد به صورت comment
کامل
توضیح داده شده است.
در
اینجا این قسمت به پایان می رسد.
در
قسمت آینده چندین مطلب پیشرفته دیگر در
ماژول نویسی هسته لینوکس را مورد بررسی
قرار خواهیم داد.
ترجمه و تکمیل :
سعید تقوی
s.taghavi@ece.ut.ac.ir
[1].
/images/down/syscall.tar.bz2
[2].
/images/down/blockproc.tar.bz2
منابع
:
1.
http://www.tldp.org/LDP/lkmpg/2.6/html
2.
http://www.linuxhq.com/guides/LKMPG/mpg.html
PDF Version
|