0% found this document useful (0 votes)
21 views139 pages

Device Driver in Linux - Ver 2.4.3

Uploaded by

farzin.vaezpour
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views139 pages

Device Driver in Linux - Ver 2.4.3

Uploaded by

farzin.vaezpour
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 139

‫به نام خدا‬

‫دیوایس درایورها در لینوکس‬

‫گردآوری شده از مجموعه مقال ت دیوایس درایور از سایت ‪linuxforu.com‬‬


‫نویسنده ‪Anil Kumar Pugalia :‬‬
‫ایمیل ‪[email protected] :‬‬

‫ترجمه ‪ :‬حمید مزرعه ملیی‬


‫ایمیل ‪[email protected] :‬‬

‫بهار ‪۱۳۹۲‬‬

‫صفحه‪1‬‬
‫سرفصل‬
‫فصل اول ‪ :‬آشنایی با درایور ‪3.............................................................................................................................................‬‬
‫فصل دوم ‪ :‬نوشتن اولین دیوایس درایور ‪7........................................................................................................................‬‬
‫فصل سوم ‪ :‬امکانا ت اضافی که ‪ c‬برای نوشتن دیوایس درایور در لینوکس به ما میدهد‪13.....................................‬‬
‫فصل چهارم ‪ :‬درایورهای کاراکتری در لینوکس ‪18.........................................................................................................‬‬
‫فصل پنجم‪ :‬ساختن فایل دستگاههای کاراکتری ‪24.........................................................................................................‬‬
‫فصل ششم ‪ :‬خواندن و نوشتن بروی فایل دستگاههای کاراکتری ‪31............................................................................‬‬
‫فصل هفتم ‪ :‬دسترسی به سخت افزار در لینوکس ‪36......................................................................................................‬‬
‫فصل هشتم ‪ :‬دسترسی به پور ت ورودی‪/‬خروجی خاص در معماری ‪44............................................................ x86‬‬
‫فصل نهم ‪ :‬کنترل ورودی‪/‬خروجی در لینوکس ‪49..........................................................................................................‬‬
‫فصل دهم ‪ :‬دیباگ کردن کرنل در لینوکس ‪64...............................................................................................................‬‬
‫فصل یازدهم ‪:‬درایورهای ‪USB‬درلینوکس‪-‬قسمت اول ‪68...........................................................................................‬‬
‫لدوازدهم‪:‬درایورهای ‪USB‬درلینوکس‪-‬قسمت دوم ‪76...........................................................................................‬‬
‫فص د‌‬
‫لسیزدهم‪:‬انتقال اطلعا ت از‪/‬به دستگاه ‪84..................................................................................................... USB‬‬
‫فص د‌‬
‫لچهاردهم‪:‬آشنایی با مبانی دیسک سخت و پارتیشن ‪92..........................................................................................‬‬
‫فص د‌‬
‫لپانزدهم‪:‬ساخت یک دیسک در حافظه ‪100............................................................................................... RAM‬‬
‫فص د‌‬
‫لشانزدهم‪:‬کاوش در کرنل و آشنایی با ‪126............................................................................................... proc/‬‬
‫فص د‌‬
‫لهفدهم‪:‬چگونگی تعامل بین ماژولها ‪132....................................................................................................................‬‬
‫فص د‌‬

‫صفحه‪2‬‬
‫فصل اول ‪ :‬آشنایی با درایور‬
‫مقدمه‬
‫همانگونه که یک راننده‪ ،‬اتومبیل خود را میراند و کنترل میکند‪ ،‬یک دیوایس درایور نیز همین کار را بببا‬
‫قسمتی از سخت افزار )مانند ماوس‪،‬صفحه کلید‪ ،‬وب کم و‪ (...‬انجام میدهببد‪.‬هببر قسببمت از یببک سببخت افببزار‬
‫میتواند با قطعه کدی که به آن »دیوایس درایور« گویندکنترل شود ویا حتی یک سببخت افببزار میتوانببد توسببط‬
‫سخت افزار دیگری که با دیوایس درایور کنترل میشود ‪ ،‬کنترل شود‪.‬‬
‫همچنین هر دستگاه یک کنترل گر دارد )مانند کنترلرهارد‪ ،‬کنترلر صببفحه نمببایش و‪ (...‬کببه بببه آن »دیببوایس‬
‫‪ (IDE,PCI,USB‬تعامببل را‬ ‫هببا)ماننببد ‪SPI,L2C‬‬ ‫کنترلر« گویند که این دیوایس کنترلرها با استفاده از »باس درایور«‬
‫ایجاد میکنند‪.‬‬

‫کنترلرها معمول با استفاده از ‪ BUS‬مخصوص شان )مثل ‪ PCI BUS‬یا ‪ IDE BUS‬و‪ (...‬به ‪ CPU‬متصل‬
‫میشوند‪.‬امروزه و در دنیای دستگاههای ‪ embeded‬میتوان گفت که به علت کاهش هزینه و کوچک سازی‪،‬‬
‫‪ BUS‬ها با ‪ CPU‬ها در یک تراشه مجتمع شده اند‪ .‬البته در صورتی کببه درایببور ببباس بببه گببونه ای خبباص‬
‫طراحی نشده باشد ‪ ،‬ما نیازی نداریم که برای این نوع دستگاهها درایور های خود را تغییر دهیم‪.‬‬

‫قسمتهای مختلف یک درایور‬


‫طهای سخت افزاریی هستند که با استفاده از پروتکل مخصوص‪ ،‬ارتببباط بیببن سببخت‬
‫باس داریورها راب د‌‬

‫صفحه‪3‬‬
‫مبندی در پایین لیه نرم افزاری یک سیستم عامل دیده میشوند‪ .‬در این‬
‫افزارها را فراهم میکنند‪.‬و از لحاظ تقسی د‌‬
‫تقسیم بندی بالی باس درایورها‪ ،‬دیوایس درایورها قرار میگیرند‪.‬‬

‫یک دیوایس درایور به دو بخش تقسیم میشود‪:‬‬


‫معاملهای مختلف مشابه میباشد و بیشتر کار ترجمه و تفسیر‬
‫‪ - ۱‬بخش سخت افزار‪ :‬این بخش در سیست د‌‬
‫ت افزار به برنامد‌هها را انجام میدهد‪.‬یک دیتاشیت شامل مستندا ت و توضیحا ت فنببی‬
‫تهای سخ د‌‬
‫دادد‌هها و دیتاشی د‌‬
‫برای یک دستگاه سخت افزاری است که عملکرد‪،‬کارایی‪،‬چگونگی برنامه نویسی و‪ ...‬درمورد آن دسببتگاه سببخت‬
‫افزاری را توضیح میدهد‪ .‬درادامه مثالهایی از چگونگی تفسیر این دیتاشیت ها را خواهیم آموخت‪.‬‬
‫‪ - ۲‬بخش سیستم عامل‪:‬این بخش وظیفه برقراری ارتباط بین بخش اول )بخش سخت افزار( و سیستم‬
‫لهبای‬
‫عامل را بعهده دارد‪.‬با توجه به تفاوتهای بنیادین سیستم عاملها‪ ،‬بصور ت طبیعی این بخش در سیستم عام د‌‬
‫مختلف )از جمله ویندوز یا لینوکس ‪ (...‬با یکدیگر متفاو ت خواهند بود‪.‬بنابراین بعنوان مثال نمیتوان یک داریببور‬
‫ویندوزی را در لینوکس و بالعکس اجرا کرد‪.‬‬

‫صفحه‪4‬‬
‫حوزه عمل یک درایور‬
‫یک دیوایس درایور در لینوکس‪ ،‬یک ‪ System call‬را برای کاربر مهیا میکند‪System call.‬‬
‫مرزی بین فضای کرنل و فضای کاربر میباشد‬
‫‪.‬‬

‫دربخش سیستم عامل‪،‬یک درایور به یکی از سه حوزه زیر میتواند تعلق بگیرد‪:‬‬
‫‪ -۱‬حوزه شبکه یا پکت گرا‬
‫‪ -۲‬حوزه رسانه های ذخیره سازی یا بل ک گرا‬
‫‪ -۳‬حوزه کاراکتر یا بایت گرا‬

‫لینوکس‪،‬مانند تمام سیستم عامل ها ‪ ۵‬کار مدیریتی را انجام میدهد‪ .‬ایببن ‪ ۵‬کببار عبارتنببد از مببدیریت‬
‫پردازش ‪ ،‬مدیریت حافظه ‪ ،‬مدیریت شبکه ‪ ،‬مدیریت رسانه های ذخیره کننده اطلعا ت و در نهایت مببدیریت‬
‫سخت افزارهای ورودی و خروجی‪.‬اگرچه میتوان پردازش و حافظه را نیز بعنوان دیوایس درایور در نظر گرفت‪،‬‬
‫لیکن بدلیل نسبی بودن آنها و دلیلی دیگر آنها را بصور ت مجزا در نظر میگیریم‪.‬‬
‫یک سیستم عامل یا بصور ت یکپارچه و غیر قابل تغییر اجرا میگردد یببا اینکببه یببک حببداقلی را دارد و‬
‫میتوان آنرا حین اجرا قبض و بسط داد‪.‬به هر ترتیب اضافه کردن کد در حین اجببرا ‪ ،‬در دو حببوزه پببردازش و‬
‫یهای امروزی به مراتب سخت و پیچیده میباشد‪.‬بنابراین اضافه و حذف کببردن‬
‫حافظه برای ‪ CPU‬ها و معمار د‌‬
‫کد درحین اجرا برای این دو حوزه برعکس سه حوزه دیگر‪ ،‬تقریبا غیر ممکن است‪.‬پببس وقببتی در آینببده مببا از‬

‫صفحه‪5‬‬
‫دیوایس درایور میگوییم‪ ،‬منظور فقط سه حوزه دیگر است‪.‬‬
‫حال میخواهیم عمیق تر وارد این سه حوزه شویم‪.‬‬
‫حوزه شبکه به دو قسمت ‪ -۱‬درایور پروتکل استک شبکه و ‪ -۲‬درایور کار ت شبکه )‪ (NIC‬که منظببور‬
‫همان ‪ Wi-Fi‬و ‪ Ethernet‬است تقسیم میشود‪.‬‬
‫حوزه رسانه های ذخیره ساز نیز به دو قسمت ‪ -۱‬درایور فایل سیستم برای بکارگیری فرمتهای مختلف‬
‫پارتیشنها و ‪ -۲‬درایور بل ک برای پروتکلهای ذخیره سازی مانند ‪ IDE‬و ‪ SCSIMTD‬تقسیم میگردد‪.‬‬

‫درایور های چند حوزه ای‬


‫بعضی سخت افزارها بجای استفاده از یک حوزه‪ ،‬همزمان میتوانند از چندین حوزه استفاده کنند‪USB.‬‬
‫ل ‪ USB‬برای کار با ‪WiFi‬‬
‫و ‪ PCI‬نمونه هایی از این سخت افزارها میباشند‪ .‬بطور مثال شما میتوانید یک دانگ ِ‬
‫ی ‪ ، USB‬یا حتی یک ‪ USB‬برای تبدیل پور ت موازی به سریال داشته باشید‪.‬همه آنهببا ‪USB‬‬
‫‪ ،‬یک قلم نور ِ‬
‫هستند ولی در سه حوزه مختلف کار میکنند‪.‬‬
‫درلینوکس معموا ًل این نوع سخت افزارها به دو بخش یا به دو درایور مختلف تقسببیم میشببوند‪ .‬بخببش‬
‫اول کنترل کننده سخت افزار و بخش دوم یک لیه انتزاعی که بالی حببوزه مببورد نظببر قرارمیگیببرد و هسببته‬
‫نامیده میشود‪.‬‬

‫جمع بندی فصل اول‬


‫یک دیوایس درایور یک تکه برنامه است که یک سخت افزار را کنترل میکند‪.‬بببا وجببود ایببن بببه چنببد‬
‫یگببوییم مثلا ً درایببور فایببل‬
‫دسته تقسیم میشود‪.‬اگر فقط یببک سببخت افببزار را کنببترل کنببد بببه آن درایببور م د‌‬
‫سیستم‪.‬بنابراین تمام دیوایس درایورها ‪ ،‬درایور هستند ولی هر درایوری لزوما ًا دیوایس درایور نیست‪.‬‬

‫صفحه‪6‬‬
‫فصل دوم ‪ :‬نوشتن اولین دیوایس درایور‬

‫درلینوکس برخلف بسیاری از سیستم عاملهای موجود‪ ،‬بببا اضببافه یببا حببذف کببردن یببک درایببور بببه‬
‫معامل میتوانببد از درایببور بصببور ت‬ ‫معامل‪ ،‬احتیاجی به راه اندازی مجدد‪ 1‬سیست د‌‬
‫معامل نمیباشببد و سیسببت د‌‬ ‫سیست د‌‬
‫بلدرنگ تاثیر بپذیرد‪ .‬اصطلحا ًا به این روش ‪ dynamic loading and unloading‬و به این‬
‫درایورها »ماژول« گفته میشود که این ماژولها در فایلهای مخصوصی با پسوند ‪ 2*.ko‬قرار میگیرند‪.‬‬
‫معامل لینوکسی بصور ت استاندارد یک جایی برای تمامی ماژولها در نظر گفته شببده اسببت و آنهببا‬
‫در هر سیست د‌‬
‫مشببببابه سبببباختار سببببورس کرنببببل لینببببوکس سببببازماندهی شببببده انببببد‪.‬شبببباخه مببببورد نظببببر‬
‫‪ /lib/modules/<kernel_version>/kernel‬میباشد که >‪ <kernel_version‬نسببخه کرنببل‬
‫میباشد و از خروجی دستور ‪ uname -r‬میتوان آنرا بدست آورد‪.‬یک نمونه از آن را در شکل زیر مشبباهده‬
‫میکنید‪.‬‬

‫‪reboot 1‬‬
‫‪Kernel object 2‬‬

‫صفحه‪7‬‬
‫معامل از دستورا ت زیرکه در شبباخه ‪ /sbin‬قراردارنببد اسببتفاده‬
‫برای اضافه و حذف کردن درایو ها به سیست د‌‬
‫معامل اضافه و حذف شود‪.‬‬
‫میشود‪.‬نکته مهم این است که یک درایور باید حتما ًا با یوزر ‪ root‬به سیست د‌‬

‫دستورات کار با درایورها‬


‫معامل‬
‫‪ : lsmod‬جهت لیست کردن کلیه ماژولهای اضافه شده به سیست د‌‬
‫معامل‬
‫>‪ : insmod <module‬جهت اضافه کردن یک ماژول به سیست د‌‬
‫معامل‬
‫>‪ : rmsmod <module‬جهت حذف کردن یک ماژول از سیست د‌‬
‫معامل‬
‫>‪ : modprobe <module‬برای اضافه کردن یک ماژول به همراه تمام وابستگیهای آن به سیست د‌‬
‫معامل را خببواهیم‬
‫بعنوان مثال در شکل زیر مراحل اضافه کردن درایور راد‌ه انداز فایل سیستم ‪ FAT‬بببه سیسببت د‌‬
‫یتر لینوکس ‪ vfat.ko‬میباشدو در مسیر‬
‫دهید‪.‬توجه کنید که این درایور بنام ‪ fat.ko‬یا در نسخد‌ههای قدیم د‌‬
‫‪ / lib/modules/`uname -r`/kernel/fs‬میتوان آنرا پیدا کرد‪.‬نکته مهم این است که ممکن است‬
‫این فایل بصور ت فشرده و با پسوند ‪ *.gz‬باشدکه میبایست نخست بوسیله برنامه ‪ gunzip‬از حالت فشببرده‬
‫خارج گردد‪.‬‬

‫صفحه‪8‬‬
‫در مثال فوق ماژول ‪ vfat‬بببه مبباژول ‪ fat‬وابسببته میباشببد‪.‬بنببابراین بایببد نخسببت ‪ fat.ko‬را لببود‬
‫نماییم‪.‬جهت اتوماتیک کردن فرآیند ‪ unzip‬و لود کردن ماژول بببا تمببام وابسببتگیهای آن میتببوان از دسببتور‬
‫‪ modprob‬استفاده نمود‪.‬دقت نمایید که دستور ‪ modprob‬احتیاج به مشخص کردن پسوند ‪ .ko‬ندارد‬
‫معامل‬
‫لها از سیسببت د‌‬
‫و نباید نام ماژول را با پسوند آن مشخص نمود‪.‬در مثال فوق در نهایت ما برای حذف ماژو د‌‬
‫از دستور ‪ rmmod‬استفاده کردیم‪.‬‬

‫نوشتن اولین درایور برای لینوکس‬


‫قبل از اینکه نوشتن اولین درایور خود را شروع کنیم لزم است که نکبباتی را در مببورد برنببامه نویسببی‬
‫ع درون یببک‬
‫درایور بدانیم‪.‬نخست اینکه یک درایور نمیتواند خودش را فراخوانی کند و درست ماننببد یببک تبباب ِ‬
‫کتابخانه کد‪، 3‬باید از بیرون فراخوانی شود‪.‬دوم اینکه یک درایور در لینوکس به زبببان ‪ C‬نوشببته م د‌‬
‫یشببود ولببی‬
‫برخلف برنامد‌ههای معمولی ‪ C‬تابع ‪ main‬ندارد‪.‬سوم اینکه درایور به کرنل الصبباق میشببودولزم اسببت کببه‬

‫‪library 3‬‬

‫صفحه‪9‬‬
‫مانند کرنل کامپایل شود و هدرفایلهای مورد نیباز بایبد از مسبیر سبورس کرنبل معرفبی شببوند نبه از مسبیر‬
‫‪. /usr/include‬‬
‫بگر است‪.‬تابع سببازنده‬
‫هر درایور همانند برنامه نویسی شی گرا داری یک تابع سازنده و یک تابع تخری د‌‬
‫بگر موقعی که درایور بببا‬
‫در موقعی که درایور با موفقیت به کرنل اضافه شد اجرا میگردد و برعکس تابع تخری د‌‬
‫موفقیت از کرنل حذف گردید)با استفاده از دستور ‪ (rmmod‬اجرا میگردد‪ .‬این دو تابع مانند دو تابع معمولی‬
‫میباشند با این تفاو ت که این توابع با استفاده از ماکروهای )(‪ module_init‬و )(‪ module_exit‬کببه در‬
‫هدرفایل کرنل ‪ module.h‬موجود است تعریف میشوند‪.‬‬
‫حال بیایید به کد اولین درایور نگاهی بیاندازیم‪:‬‬

‫‪/* ofd.c – Our First Driver code */‬‬


‫>‪#include <linux/module.h‬‬
‫>‪#include <linux/version.h‬‬
‫>‪#include <linux/kernel.h‬‬

‫‪static int __init ofd_init(void) /* Constructor */‬‬


‫{‬
‫;)"‪printk(KERN_INFO "Namaskar: ofd registered‬‬
‫;‪return 0‬‬
‫}‬

‫‪static void __exit ofd_exit(void) /* Destructor */‬‬


‫{‬
‫;)"‪printk(KERN_INFO "Alvida: ofd unregistered‬‬
‫}‬

‫;)‪module_init(ofd_init‬‬
‫;)‪module_exit(ofd_exit‬‬

‫;)"‪MODULE_LICENSE("GPL‬‬
‫;)">‪MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com‬‬
‫;)"‪MODULE_DESCRIPTION("Our First Driver‬‬

‫صفحه‪10‬‬
‫برنامه فوق اولین درایور میباشد و البته یک درایور کامل است‪.‬نام این برنببامه را ‪ ofd.c‬میگببذاریم‪.‬در‬
‫نظر داشته باشیدکه برخلف برنامد‌ههای معمول ‪ c‬در درایورها‪ ،‬خبری از هدرفایلهایی ماننببد ‪ stdio.h‬نیسببت‬
‫بدین علت که این هدرفایلها در فضای یوزر میباشند‪ .‬بجای آنها از هدر فایلهای فضای کرنل مانند ‪kernel.h‬‬
‫استفاده مگردد و به همین دلیل است که )(‪ printk‬جایگزین )(‪ printf‬شده است‪.‬‬
‫هدر فایل ‪ version.h‬جهت سازگاری نسخه درایور با نسخه کرنلببی اسببت کببه آن درایببور را لببود‬
‫میکند و همچنین ماکروهای *_‪ MODULE‬نیز اطلعا ت درایور را مشخص میکند و مشابه امضاء برای درایور‬
‫است‪.‬‬

‫ساختن اولین درایور در لینوکس‬


‫در قسمت قبل ما اولین درایور را نوشتیم و حال ما یک فایل به زبان ‪ c‬داریم که آنرا کامپایل کببرده و‬
‫فایل ‪ ofd.ko‬را بسازیم‪ .‬فایل ‪ Makefile‬زیر سورس لزم جهت ساخت درایور را به ما میدهدو با اسببتفاده‬
‫از آن میتوانیم اولین درایور خود را بسازیم‪.‬برای ساخت درایببور هببا مببا نیبباز بببه سببورس کرنببل و یببا حببداقل‬
‫هدرفایلهای کرنل داریم‪ .‬فرض بر این است که سورس یا هدر فایلهببای کرنببل در مسببیر ‪/usr/src/linux‬‬
‫قرار دارد‪ .‬اگر در سیستم شما سورس کرنل یا هدر فایلهای کرنل در مسیر دیگری قراردارد میبایست در فایببل‬
‫‪ Makefile‬متغییر ‪ KERNEL_SOURCE‬را تغییر دهید‪.‬در زیر سورس ِ فایل ‪ Makefile‬آمده است‪:‬‬
‫د‌‬

‫‪# Makefile – makefile of our first driver‬‬


‫‪# if KERNELRELEASE is defined, we've been invoked from the‬‬
‫‪# kernel build system and can use its language.‬‬
‫)‪ifneq (${KERNELRELEASE},‬‬
‫‪obj-m := ofd.o‬‬
‫‪# Otherwise we were called directly from the command line.‬‬
‫‪# Invoke the kernel build system.‬‬
‫‪else‬‬
‫‪KERNEL_SOURCE :=/usr/src/linux‬‬
‫)‪PWD := $(shell pwd‬‬
‫‪default:‬‬
‫‪${MAKE} -C ${KERNEL_SOURCE} SUBDIRS=${PWD} modules‬‬
‫‪clean:‬‬
‫‪${MAKE} -C ${KERNEL_SOURCE} SUBDIRS=${PWD} clean‬‬
‫‪endif‬‬

‫صفحه‪11‬‬
‫الن فایل ‪ ofd.c‬و ‪ Makefile‬آماده است و تنها چیزی که برای ساختن درایور نیاز داریببم صببدور‬
‫دستور ‪make‬د‌ میباشد‪.‬خروجی فرمان ‪ make‬چیزی شبیه زیر خواهد بود‪:‬‬

‫‪$ make‬‬
‫‪make -C /usr/src/linux SUBDIRS=... modules‬‬
‫'‪make[1]: Entering directory `/usr/src/linux‬‬
‫‪CC [M] .../ofd.o‬‬
‫‪Building modules, stage 2.‬‬
‫‪MODPOST 1 modules‬‬
‫‪CC‬‬ ‫‪.../ofd.mod.o‬‬
‫‪LD [M] .../ofd.ko‬‬
‫'‪make[1]: Leaving directory `/usr/src/linux‬‬

‫اگر همه چیز به خوبی پیش رود‪ ،‬فایل ‪ ofd.ko‬ساخته خواهد شد و میتوانیم با دسترسببی ‪ root‬و یببا‬
‫استفاده از ‪ sudo‬کارهای معمول زیر را بروی این درایور انجام دهیم‪:‬‬
‫‪# su‬‬
‫‪# insmod ofd.ko‬‬
‫‪# lsmod | head -10‬‬

‫حال میتوانیم با استفاده از دستور آخر مشاهده کنیم که درایور ما به درستی به کرنل اضافه شده است‪.‬‬

‫صفحه‪12‬‬
‫فصل سوم ‪ :‬امکانا ت اضافی که ‪ c‬برای نوشتن دیوایس‬
‫درایور در لینوکس به ما میدهد‬

‫در فصل قبل دیدیم که در برنامه نوشته شده بجای تابع )(‪ printf‬از تببابع )(‪ printk‬اسببتفاده شببده‬
‫است ولی برخلف انتظار در خروجی نوشتد‌های را مشاهده نکردیم‪.‬برای دیدن نوشته ی این تببابع بعببد از اضببافه‬
‫شدن به سیستم عامل میبایست دستور ‪ dmesg |tail‬را صادر کنیم‪ .‬اینکه چببرا نوشببتد‌هها بجببای کنسببول‬
‫معمولی از طریق این دستور میبایست مشاهده شود را در ادامه توضیح خواهیم داد‪.‬‬

‫مسیج لهگهای کرنل‬


‫یشبویم کبه پارامترهببای )( ‪ printf‬و )(‬
‫در یک بررسببی و جسببتجوی کوچبک در اینببترنت متببوجه م د‌‬
‫نها این است کببه )(‪ printf‬در فضببای یببوزر و )(‪ printk‬در‬
‫‪ printk‬مشابه یکدیگر میباشند ولی تفاو ت آ د‌‬
‫فضای کرنل مورد استفاده قرار میگیرد‪.‬همچنین در فضای کرنببل دیگببر مببا از فرمتهببایی نظیببر ‪ %f‬و ‪ %if‬و‪...‬‬
‫استفاده نمیکنیم‪.‬مهم این است که )(‪ printk‬برعکس )(‪ printf‬برای چاپ خروجببی بببروی کنسببول طراحببی‬
‫ل نمی تواند انجام دهد و دلیل آن هم این است که بعضی فرآینببدها‬
‫نشده است‪ .‬درحقیقت همچین کاری را اص ا ً‬
‫یشوند‪ .‬مانند کتابخاند‌ههای کد‪،‬که وقتی در سطح یوزر یا کرنل فراخوانی می شوند‪ ،‬اجرا‬
‫در پشت صحنه انجام م د‌‬
‫یگردند ولی خروجی مورد مشاهده ای را تولید نمیکنند‪.‬‬
‫م د‌‬
‫در مورد )(‪ printk‬نیز همین گونه است که با اجببرای آن ‪،‬خروجببی بجببای کنسببول در لگ سیسببتم‬
‫ریخته میشود‪ syslog.‬نیز دیمانی‪ 4‬است که در سطح یوزر اجرا شده و پیامهای ریخته شببده در لگ سیسببتم‬
‫را گرفته وبعد به جایی که در فایل ‪ /etc/syslog.conf‬مشخص گردیده میفرستد‪.‬‬
‫‪ printk‬اسببتفاده‬
‫د‌‬ ‫شببما در برنببامه فصببل قبببل از مبباکروی ‪ KERN_INFO‬در پببارامتر دسببتور )(‬
‫کردید‪.‬این ماکرو یک رشته ثابت است که به ابتببدای رشببته بعببد متصببل گردیببده و رشببته نهببایی را تشببکیل‬
‫یبینیم‪:‬‬
‫میدهد‪.‬برای یاد آوری یک خط از برنامه فصل قبل را دوباره با هم م د‌‬

‫;)"‪printk(KERN_INFO "Namaskar: ofd registered‬‬

‫‪deamon 4‬‬

‫صفحه‪13‬‬
‫همانگونه که مشاهده میکنید بین متن اصلی و ‪ KERN_INFO‬هیچ کاراکتری از قبیل کاما وجود ندارد‪ .‬بدین‬
‫معنی که این ماکرو‪ ،‬رشتد‌های دیگببر یببا یببک آرگومببان مجببزا نیسببت‪.‬در سببورس کرنببل لینببوکس و در فایببل‬
‫‪ linux/kernel.h‬هشت نوع دیگر از این ماکروها تعریف شده اند‪:‬‬

‫‪#define KERN_EMERG "<0>" /* system is unusable‬‬ ‫‪*/‬‬


‫‪#define KERN_ALERT "<1>" /* action must be taken immediately‬‬ ‫‪*/‬‬
‫‪#define KERN_CRIT "<2>" /* critical conditions‬‬ ‫‪*/‬‬
‫‪#define KERN_ERR "<3>" /* error conditions‬‬ ‫‪*/‬‬
‫‪#define KERN_WARNING "<4>" /* warning conditions‬‬ ‫‪*/‬‬
‫‪#define KERN_NOTICE "<5>" /* normal but significant condition */‬‬
‫‪#define KERN_INFO "<6>" /* informational‬‬ ‫‪*/‬‬
‫‪#define KERN_DEBUG "<7>" /* debug-level messages‬‬ ‫‪*/‬‬

‫‪ syslog‬پیامهببای‬
‫د‌‬ ‫حال بسته به اینکه ما از کدام یک از ماکروهای فوق استفاده کرده باشببیم‪ ،‬دیمببان‬
‫یکند‪.‬یک مقصد برای هر کدام از ایببن‬
‫نها به جاهایی که تعریف شده است منتقل م د‌‬
‫دریافتی را بر اساس نوع آ د‌‬
‫شفببرض تمببام ماکروهببا‬
‫یتواند فایل ‪ /var/log/messages‬باشد‪.‬از آنجایی که ایببن فایببل پی د‌‬
‫ماکروها م د‌‬
‫یشود‪ .‬اگرچه شما میتوانید با تغییر تنظیما ت‪ ،‬خروجی را‬
‫میباشد تمام خروجی ‪ printk‬نیز به آن فایل ریخته م د‌‬
‫ل ‪ (/ dev/ttys0‬یا حتی به تمام کنسولها )پیش فرض لینببوکس در موقببع بببروز خطببای‬
‫به پور ت سریال )مث ا ً‬
‫‪ (KERN_EMERG‬منتقل کنید‪.‬‬
‫از آنجاییکه تمام پیامهای نوشته شده در فایل ‪ /var/log/messages‬فقط شامل پیامهای کرنل نمیباشببد‬
‫و تمام پیامها )پیامهای چندین دیمان سطح یوزر( را شامل میگردد و همچنین این فایل معموا ًل بببرای یوزرهببای‬
‫معمولی قابل خواندن نیست بنابراین یک ابزار سطح کاربر بنام ‪dmesg‬د‌ تهیه گردیده است تا فقببط پیامهببای‬
‫کرنل را تفسیر و بروی خروجی استاندارد نمایش دهد‪.‬شکل زیر طرز کار این ابزار را نشان میدهد‪:‬‬

‫صفحه‪14‬‬
‫ابزار خاص ‪ GCC‬برای برنامه نویسی سطح کرنل‬
‫در این قسمت لزم به ذکر است که ‪ __init‬و ‪ __exit‬کلمببا ت کلیببدی زبببان ‪ C‬نیسببتند‪.‬در حقیقببت‬
‫کرنل لینوکس تنها با استفاده از زبان ‪ C‬و همچنین ابزارهای جانبی کامپایلر زبان ‪ C‬یا همان ‪ GCC‬نوشته شده‬
‫است‪ .‬و ماکروهای ‪ __init‬و ‪ __exit‬تنها دونمونه از این ابزارهای جانبی ‪ GCC‬هسببتند‪.‬ایببن دو مبباکرو هیببچ‬
‫ارتباطی با لود کردن درایور درکرنل و بصور ت داینامیک ندارد بلکه هر زمان که بخببواهیم کببدی بببرای کرنببل‬
‫نبویسیم از این دو ماکرو استفاده خواهیم کرد‪.‬تمام توابعی که با ‪ __init‬مار ک دار شده باشند در موقع کامپایل ِ‬
‫کرنل‪ ،‬بصور ت اتوماتیک توسط کامپایلر ‪ GCC‬در بخش ‪ init‬مربوط به ‪ kernel image‬قرار میگیرند و‬
‫هر تابعی که با استفاده از مبباکروی ‪ __exit‬مببار ک دار شببده باشببد در بخببش ‪ exit‬مربببوط بببه ‪kernel-‬‬
‫‪ image‬قرار خواهد گرفت‪.‬‬
‫نهببا‬
‫سؤالی که ممکن است مطرح شود این است که چرا به چنین ماکروهایی نیاز داریم؟ و خاصببیت آ د‌‬
‫چیست؟ جواب این است که تمام توابع علمت گذاری شده توسط ‪init‬بب قرار است فقط یکبار و آنهببم موقببع‬
‫بال آمدن کرنل اجرا گردد و هرگز تا راه اندازی مجدد کرنل دوباره اجرا نخواهند شد‪.‬و بعداز اجببرا شببدن ایببن‬
‫توابع با آزاد شدن حافظه مخصوص ‪ ، init‬این قسمت از حافظه توسط کرنل آزاد خواهد شد‪.‬و بصور ت مشببابه‬

‫صفحه‪15‬‬
‫توابعی که با ‪ __ exit‬علمت گذاری شده باشند در موقع خاموش شدن سیستم اجرا خواهند گردید‪.‬‬
‫سؤال بعدی این است که سیستم بهرحال خاموش خواهد شد و چرا ما باید حافظه ای را به ‪exit‬بب در‬
‫تمام مدتی که کرنل در حال اجرا است اختصاص دهیم؟ پاسخ این است که این توابع تا لحظه خبباموش شببدن‬
‫قالعاده حافظه توسط لینوکس است‪.‬‬
‫ل در حافظه وجود ندارند! این یک بهینه سازی خار د‌‬
‫کرنل اص ا ً‬
‫مورد فوق تنها یکی از نموند‌ههای همکاری متقابل و زیبای بین کرنل و ‪ GCC‬اسببت کببه بهینببه سببازی‬
‫منابع را در بر خواهد داشت‪.‬و در آینده از این فو ت و فن ها نیز بیشتر خواهیم آموخت‪.‬برای همیببن اسببت کببه‬
‫یشببود‪ .‬بببه هرترتیببب ‪ GCC‬و‬
‫نها ‪ GCC‬است ‪،‬کامپایببل م د‌‬
‫کرنل لینوکس فقط توسط کامپایلرهایی که پایه آ د‌‬
‫کرنل به هم گره خورده اند‪.‬‬

‫توابع کرنلی که خطا را مدیریت میکند‬


‫آیا میدانید چگونه برنامه نویسان مختلف بروی بزرگترین پروژه لینوکس یعنی کرنل ‪ ،‬بببدون هیچگببونه‬
‫نها این اسببت کببه‬
‫تداخلی با هم کار میکنند؟ برای این سؤال دلیل مختلفی وجود دارد‪.‬یکی و شاید مهمترین آ د‌‬
‫تمام برنامه نویسان کرنل لینببوکس‪ ،‬دسببتورالعمل و شببیوه کببد نویسببی در کرنببل را میداننببد و بببه آن پایبنببد‬
‫هستند‪.‬برای مثال میتوان به دستورالعمل چگونگی بازگرداندن مقدار از یک تابع اشاره کرد‪ .‬هر تابع در کرنل به‬
‫یک مدیریت کننده خطا )‪ ( error handling‬نیاز دارد و این مدیریت کننده خطا‪ ،‬معموا ًل یک عدد صحیح‬
‫را برمیگرداند که مقدارش طبق دستورالعمل میباشد‪ .‬در صور ت اجرای صحیح تابع‪ ،‬معموا ًل عدد صفر بازگشت‬
‫ل مقدار خروجی میتواند تعببداد بایتهببای‬
‫یشود مگر آنجایی که به اطلعا ت بیشتری نیاز داشته باشیم‪ .‬مث ا ً‬
‫داده م د‌‬
‫ارسال شده باشد ولی توجه داشته باشید که این مقدار حتما ًا باید مثبت باشد‪ .‬در صببورتی کببه تببابع بببا خطببایی‬
‫یشود کببه‬
‫مواجه شود‪ ،‬کد خطا به ماکروهایی که در هدر فایل ‪ /linux/errno.h‬مشخص شدد‌هاند منتقل م د‌‬
‫در آنجا توسط این ماکروها علمت منفی به آن اضافه میشود‪.‬چندین هدر فایل برای اعداد خطا وجود دارند که‬
‫‪ //asm-generic/errno-base.h‬و ‪ /asm-generic/errno.h‬و ‪/asm/errno.h‬‬
‫نها میباشند‪.‬‬
‫نموند‌ههایی از آ د‌‬

‫رابطه کرنل لینوکس با ‪C‬‬


‫اگر بخاطر داشته باشید نمیتوانستیم از ‪ /usr/include‬در برنامد‌ههای کرنل استفاده کنیببم‪.‬از طببرف‬
‫دیگر میدانیم که کرنل لینوکس را نیز با زبان ‪ C‬استاندارد به اضافه چند ابزار ‪ GCC‬نوشتد‌هاند‪ .‬پس دلیل ایببن‬
‫تناقض چیست؟در حقیقت تناقضی در کببار نیسببت‪ C .‬اسببتاندارد همببان ‪ C‬خببالص اسببت و فقببط یببک زبببان‬
‫نها جببزء کتابخاند‌ههببای اسببتاندارد زبببان ‪C‬‬
‫برنامد‌هنویسی است و هدر فایلها‪ ،‬جزء زبان برنامه نویسی نیستند‪ .‬آ د‌‬
‫برای برنامه نویسی میباشند‪.‬این مطلب بدین معنی است که تمام کتابخاند‌ههای استاندارد و همچنین تمببام توابببع‬

‫صفحه‪16‬‬
‫استاندارد ‪ ،ANSI‬جزیی از ‪ C‬خالص نیستند‪.‬‬
‫پس چگونه توسعه دهندگان کرنل از ‪ C‬و بدون این کتابخاند‌هها استفاده میکنند؟ و آیببا برنببامه نویسببی‬
‫برای کرنل بدون این کتابخاند‌هها بسیار طاقت فرسا نیست؟ جواب این سؤال منفی اسببت‪ .‬بببدلیل اینکببه توسببعه‬
‫دهندگان کرنل خودشان توابع و کتابخاند‌ههای مورد نیازشان را از قبل ساختد‌هاند و این توابع نیز جزیی از کرنببل‬
‫نها است و مشابه آن توابعی نیز برای کار با رشتد‌هها ‪ ،‬حببافظه و …‬
‫یشود که ‪ printk‬نیز یکی از آ د‌‬
‫محسوب م د‌‬
‫نیز نوشته شده و جزیی از کرنل میباشد که این توابببع در جنببدین شبباخه ‪ lib‬و ‪ ipc‬و ‪ kernel‬و نظببایر آن‬
‫نها نیز در شاخه ‪ /include/linux‬میباشد‪ .‬به همین دلیل است که مببا‬
‫قرار دارند‪.‬همچنین هدر فایلهای آ د‌‬
‫برای نوشتن برنامه کرنل به سورس کرنل یا حداقل هدر فایلهای کرنل نیازمندیم‪ .‬از آنجاییکه سورس کرنببل و‬
‫هدر فایلها ‪ ،‬هر دو میتوانند مورد استفاده قرار بگیرند‪ ،‬بنابراین در لینوکس نیز برای نصب هر کدام بسته هببایی‬
‫ل اگر شما فدورا داریببد و بسببته سببورس کرنببل را نصببب کردد‌هایببد بایببد در مسببیر‬
‫متفاو ت تهیه شده است‪.‬مث ا ً‬
‫‪ / usr/src/kernels/<kernel-version‬بتوانید آنهارا بیابید ولی اگر فقط بسته هدر فایلهای کرنل را‬
‫ب فقط هدر فایلهببا شببما‬
‫نها در مسیر ‪/usr/src/linux‬بیابید‪.‬در فدورا و برای نص ِ‬
‫نصب کرده باشید باید آ د‌‬
‫کافی است که دستور ‪ yum install kernel-devel‬را صادر کنید‪ .‬اگر شما از ‪ mandriva‬استفاده‬
‫میکنید و میخواهید سورس کامل را نصب کنید کافی است که دستور ‪ urpmi kernel-source‬را صادر‬
‫کنید و همچنین برای نصببب سببورس کرنببل در ‪ ubuntu‬کببافی اسببت کببه دسببتور ‪sudo apt-get‬‬
‫‪ install linux-source‬را تایپ نمایید‪.‬‬

‫صفحه‪17‬‬
‫فصل چهارم ‪ :‬درایورهای کاراکتری در لینوکس‬

‫نهببا نیازمنببدیم‪.‬همچنیببن بببا‬


‫دراین فصل ما خواهیم آموخت که درایورها چیستند و برای چه مببا بببه آ د‌‬
‫ویژگیهای درایورهای کاراکتری نیز آشنا خواهیم شد‪.‬‬
‫اگر ما درایوری را بنویسیم که عملیا ت بایت گرا )در زبان ‪ C‬به کاراکتر معروف است( را انجام دهد آنگبباه مببا‬
‫یک درایور کاراکتری نوشته ایم‪ .‬اکثر دستگاههای موجود‪،‬کاراکتری هستند‪ .‬بنببابراین درایورهببای نوشببته شببده‬
‫نها نیز کاراکتری میباشند‪.‬برای مثال میتوان به درایورهای سریال ‪ ،‬دوربین ‪ ،‬صدا و تصویر اشاره کرد‪ .‬به‬
‫برای آ د‌‬
‫عبار ت دیگر تمام درایورهایی که به نوعی ذخیره ساز )‪ (Storage‬و یا شبببکه ای )‪ (Network‬نیسببتند را‬
‫میتوان درایورهای کاراکتری نامید‪.‬حال نگاهی به اشتراکا ت دستگاههای کاراکتری می اندازیم‪:‬‬

‫یکنید برای هر عملیا ت بروی دسببتگاههای بببایت گببرا در حببوزه‬


‫همانطور که در تصویر فوق مشاهده م د‌‬
‫تافزار‪ ،‬میبایست یک درایورکاراکتری در فضای کرنل لینوکس بین دستگاه و برنامد‌های که در فضای یببوزر‬
‫سخ د‌‬
‫ط فایلی آن دسببتگاه کببه بببه فایببل‬
‫یشود‪ ،‬وجود داشته باشد‪.‬همچنین درایورهای کاراکتری از یکسری راب ِ‬
‫اجرا م د‌‬
‫م مجازی لینک شده است اسببتفاده میکنببد‪.‬بببدین معنببی کببه برنامد‌ههبا معمببوا ًل یکسببری عملیببا ت فببایلی‬
‫سیست ِ‬
‫)خواندن‪،‬نوشتن و‪ (...‬بروی این فایلهای دستگاهها انجام میدهند و فایل سیستم مجازی این عملیا ت را بببه توابببع‬
‫مرتبط در درایورهای دستگاه کاراکتری منتقل میکنببد و در نهببایت ایببن توابببع‪ ،‬دسترسببی سببطح پببایین را بببه‬

‫صفحه‪18‬‬
‫تافزار میسر میکنند‪.‬اگرچه برنامه یکسری عملیا ت فایلی را انجام میدهد ولببی ممکببن اسببت نتایببج آن بببا‬
‫سخ د‌‬
‫تافزار مورد نظر میباشببند‪.‬بطببور‬
‫نها تنها یک راه برای تعامل با سخ د‌‬
‫عملیا ت فایلی واقعی یکی نباشد بلکه فقط آ د‌‬
‫مثال اگر در یک فایل کاراکتری ابتدا مقداری را بنویسببیم سببپس بخببواهیم همیببن مقببادیر را بخببوانیم ‪ ،‬لزومبا ًا‬
‫خروجی ‪،‬با آن چیزی که ما از یک فایل معمولی انتظار داریم یکی نخواهد بود‪.‬بعنوان یک مثببال عملببی میتببوان‬
‫ل دستگا ِه صوتی بنویسیم‪ ،‬بجای اینکه واقعا ًا در آن فایل ذخیره گردد ‪ ،‬منجر‬
‫گفت که اگر مقداری را در یک فای ِ‬
‫به تولید صدا از بلندگوی سیستم خواهد شد‪.‬و اگر بخواهیم صدایی را از میکروفن دستگاه دریافت کنیم بایببد از‬
‫این فایل بخوانیم‪ .‬بدیهی است که دادد‌ههای نوشته شده با دادد‌ههای خوانده شده از این فایل‪ ،‬ارتباطی بببا یکببدیگر‬
‫ندارند‪.‬‬
‫تافزار به چهار عنصببر ‪ -۱‬برنببامه ‪-۲‬‬
‫بنابراین برای ارتباط کامل بین یک برنامه در فضای یوزر و سخ د‌‬
‫تافزار‪ .‬نیازمندیم‪.‬‬
‫ل رابط ‪ -۳‬درایو ِر دستگاه ‪ -۴‬سخ د‌‬
‫فای ِ‬
‫نکته جالب توجه این است که هر یک از چهار عنصر فوق میتواند بروی یک کامپیوتر مجزا باشببد بببدون اینکببه‬
‫سایر عناصر در آن کامپیوتر وجود داشته باشند ولی باید به نحوی این چهار عنصر صراحتا ًا با هم متصل باشند‪.‬‬
‫ط دستگاه ) با استفاده از ‪ (system call‬با آن فایل‬
‫ل راب ِ‬
‫یک برنامه ابتدا با درخواست باز کردن فای ِ‬
‫ل رابط به درایور مرتبطی کببه در کرنبل لبود شببده اسبت )از قبببل رجیسببتر‬
‫ارتباط برقرار میکند سپس آن فای ِ‬
‫تافزا ِر کباراکتری مبورد نظبر‬
‫یشود و در نهایت آن درایور‪ ،‬عملیا ت سطح پایین را بروی سبخ د‌‬
‫گردیده( وصل م د‌‬
‫تافزار و برنامه برقرار میگردد‪ .‬نکته قابل ذکر این اسببت‬
‫انجام میدهد‪ .‬با این توضیح یک ارتباط کامل بین سخ د‌‬
‫تافزار و درایور میباشد‪.‬‬
‫که فایل رابط ‪ ،‬یک فایل واقعی نیست و تنها یک رابط بین سخ د‌‬

‫شماره های اصلی و فرعی‬


‫ل رابببط بببا‬
‫ل رابط دسترسی پیدا میکند ولببی ارتببباط فایب ِ‬
‫برنامه سطح یوزر با استفاده از نام فایل به فای ِ‬
‫ل رابط‪.‬بنابراین کاربر میتواند هببر نببامی را بببرای فای بل ِ‬
‫ل رابط میباشد و نه نام فای ِ‬
‫درایور بر اساس شمارد‌هی فای ِ‬
‫رابط خود انتخاب نماید و کرنل با استفاده از شمارد‌هی فایل با درایور مورد نظر ارتباط برقرار میکند‪.‬ایببن شببماره‬
‫ل رابط معموا ًل دارای یک زوج شمارد‌هی اصلی و شمارد‌هی فرعی میباشد‪.‬‬
‫فای ِ‬
‫تافزار‬
‫تافزار و شمارد‌هی فرعی نیز به ویژگیهای آن سخ د‌‬
‫تا قبل از کرنل ‪ ۲.۶‬شمارد‌هی اصلی به نوع سخ د‌‬
‫مبندی دیگببر اجببباری نیسببت و میتببوان چنببدین درایببور را بببا‬
‫مربوط بود ولی از کرنل ‪ ۲.۶‬به بعد ایببن تقسببی د‌‬
‫شمارد‌ههای اصلی یکسان ولی با شمارد‌ههای فرعی متفبباو ت اسببتفاده نمببود‪.‬لیکببن هنببوز بببرای سببخت افزارهببای‬
‫طهای سریال از شببمارد‌هی ‪ ، ۴‬رابببط‬
‫یشود‪.‬مثل برای راب د‌‬
‫استاندارد از شمارد‌هی اصلی مخصوص به خود استفاده م د‌‬
‫یشود‪ .‬دستور زیر دستگاهها به همراه‬
‫میکروفن از شمارد‌هی ‪ ، ۱۳‬دستگاههای صوتی از شمارد‌هی ‪ ۱۴‬و‪ ...‬استفاده م د‌‬
‫شماره های اصلی و فرعی آنرا بروی کامپیوتر شما نشان میدهد‪:‬‬

‫صفحه‪19‬‬
‫”‪$ ls -l /dev/ |grep “^c‬‬

‫ارتباط شماره‌ههای اصلی و فرعی در کرنل ‪ ۲.۶‬به بعد‬


‫نوع دستگاهی که در هدر فایل ‪ linux/types.h‬تعریف شده است شامل ‪ dev_t‬است که معرف‬
‫هر دو شمارد‌هی اصلی و فرعی میباشد‪.‬همچنین ماکروهای زیر نیز در هببدر فایبل ‪ linux/kdev_t.h‬تعریبف‬
‫شده است‪:‬‬
‫)‪ MAJOR(dev_t dev‬که شمارد‌هی اصلی دستگاه را برمیگرداند‪.‬‬
‫)‪ MINOR(dev_t dev‬که شمارد‌هی فرعی دستگاه را برمیگرداند‪.‬‬
‫)‪ MKDEV(int major , int minor‬که یک دستگاه را بر اساس شمارد‌ههای اصلی و فرعی میسازد‪.‬‬
‫همچنین جهت اتصال به فایل دستگاه دو مرحله لزم است که انجام گیرد‪:‬‬
‫‪ -۱‬ثبت رنج شمارد‌ههای اصلی و فرعی برای دستگاه‪.‬‬
‫‪ - ۲‬ارتباط بین هر عملیا ت بروی فایل دستگاه )اعم از خواندن‪،‬نوشتن و‪ (...‬و تابعی در درایور کببه وظیفببه هنببدل‬
‫کردن آن عملیا ت را دارد‪.‬‬
‫گام اول با یکی از دو ‪ API‬زیرکه در هدر فایل ‪ linux/fs.h‬وجود دارد قابل انجام است‪:‬‬

‫;)‪int register_chrdev_region(dev_t first, unsigned int cnt, char *name‬‬


‫‪int alloc_chrdev_region(dev_t *first, unsigned int firstminor, unsigned int cnt, char‬‬
‫;)‪*name‬‬

‫یباشد‪ .‬این شماره با نامی کببه بببا متغیببر ‪first‬‬


‫در ‪API‬د‌ ی اول ‪ ،‬مقدار ‪ cnt‬شمارد‌هی مربوط به فایل دستگاه م د‌‬
‫ی آزاد )‪،(major‬‬
‫تعریف شده است آغاز میگردد‪.‬ولی در ‪ API‬ی دوم بصور ت داینامیک اولیببن شببماره اصببل ِ‬
‫یشود و ‪ cnt‬ی مربوط به شمارد‌هی دستگاه که با ترکیب شمارد‌ههای اصلی و فرعی انتخاب شده‬
‫برگشت داده م د‌‬
‫یشود ‪ ،‬ایجاد میگردد‪.‬از هر کدام از ‪ API‬های فوق‬
‫است که با >‪ <free major, first minor‬شروع م د‌‬
‫که استفاده نماییم در ‪ / proc/devices‬ما میتوانیم نام با شماره اصلی ثبت شببده را ببببینیم‪.‬اکنببون بببا ایببن‬
‫اطلعا ت ما میتوانیم اولین درایور خود را اینگونه بنویسیم‪:‬‬

‫>‪#include <linux/types.h‬‬
‫>‪#include <linux/kdev_t.h‬‬
‫>‪#include <linux/fs.h‬‬
‫‪static dev_t first; // Global variable for the first device number‬‬

‫صفحه‪20‬‬
:‫ مینویسیم‬constructor ‫که ما برای‬

if (alloc_chrdev_region(&first, 0, 3, "Shweta") < 0)


{
return -1;
}
printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));

:‫ مینویسیم‬destructor ‫و برای‬

unregister_chrdev_region(first, 3);

:‫بنابراین برنامه کامل چیزی شبیه کد زیر خواهد بود‬

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>

static dev_t first; // Global variable for the first device number

static int __init ofcd_init(void) /* Constructor */


{
printk(KERN_INFO "Namaskar: ofcd registered");
if (alloc_chrdev_region(&first, 0, 3, "Shweta") < 0)
{
return -1;
}
printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));
return 0;
}

static void __exit ofcd_exit(void) /* Destructor */

21‫صفحه‬
‫{‬
‫;)‪unregister_chrdev_region(first, 3‬‬
‫;)"‪printk(KERN_INFO "Alvida: ofcd unregistered‬‬
‫}‬

‫;)‪module_init(ofcd_init‬‬
‫;)‪module_exit(ofcd_exit‬‬

‫;)"‪MODULE_LICENSE("GPL‬‬
‫;)">‪MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com‬‬
‫;)"‪MODULE_DESCRIPTION("Our First Character Driver‬‬

‫خب پس از اینکه کد فوق را ذخیره کردیم میتوانیم کارهای معمولی که بروی هر درایور انجام میدهیم را اجرا‬
‫کنیم‪:‬‬
‫• ساخت درایور )یا همان فایل ‪ (.ko‬بوسیله اجرای دستور ‪make‬‬
‫• لود کردن درایور با استفاده از دستور ‪insmod‬‬
‫• مشاهده درایور لود شده با استفاده از دستور ‪lsmod‬‬
‫• حذف درایور از کرنل با استفاده از دستور ‪rmmod‬‬

‫قبل از حذف درایور از کرنل‪ ،‬برای دیدن شمارد‌هی اصلی ثبت شده که با کلمه ‪ shweta‬شببروع شببده اسببت‬
‫میتوانیم با دستور ‪ cat /proc/devices‬نگاهی به آن داشته باشیم‪.‬میتوان دید که آن فایل واقع با ًا وجببود‬
‫دارد ولی ما نمیتوانیم فایل دستگاهی را در ‪ / dev‬با آن شمارد‌هی اصلی بیابیم‪ .‬البته میتوانیم با استفاده از دستور‬
‫‪ mknod‬بصور ت دستی آنرا بسازیم و سعی کنیم در آن بنویسیم و یا از آن بخببوانیم‪ .‬بببه شببکل زیببر تببوجه‬
‫فرمایید‪:‬‬

‫صفحه‪22‬‬
‫نکته ایجاست که شماره ‪ ۲۵۰‬میتواند در سیستم شما متفاو ت باشد‪.‬شکل فوق همچنین غیببر موفببق بببودن کببار‬
‫خواندن و نوشتن بروی این فایل را نیز نشان میدهد‪.‬بیاد بیاورد که گام دوم ‪ ،‬ارتببباط عملیببا ت فایببل بببا توابببع‬
‫درایور بود که ما در اینجا هنوز انجام ندادد‌هایم‪ .‬بنابراین هنوز به اطلعا ت بیشتری نیاز داریم که در فصببل بعببد‬
‫به آن خواهیم پرداخت‪.‬‬

‫صفحه‪23‬‬
‫فصل پنجم‪ :‬ساختن فایل دستگاههای کاراکتری‬
‫در قسمت قبل دیدیم که فقط ثبت زوج شمارد‌ههای اصلی و فرعی برای کار با فایلهای دستگاه کاراکتری‬
‫کافی نیست و فایل آن در شاخه ‪ / dev‬ساخته نخواهد شببد و همببانگونه کببه دیببدیم اینکببار را بببا اسببتفاده از‬
‫‪ mknode‬و بصور ت دستی انجام دادیم‪.‬بنابراین در این فصل ما قصد داریم کببه بببا اسببتفاده از ‪udevdv‬‬
‫بصور ت اتوماتیک اینکار را انجام دهیم و همچنین دیوایس درایور را با این فایل ارتباط خببواهیم داد و عملیببا ت‬
‫بروی این فایل را با توابع موجود در دیوایس درایور مرتبط خواهیم نمود‪.‬‬

‫ل دستگاه بصورت اتوماتیک‬


‫ساخت فای ِ‬
‫ل دستگاه بببا اسببتفاده از فراخببوانی ‪ API‬هببای مرتبببط بببا ‪devfs‬‬
‫تا قبل از کرنل ‪ ۲.۴‬کار ساخت فای ِ‬
‫بصور ت اتوماتیک توسط کرنل انجام میگرفبت‪.‬بببا توسبعه و تکامببل کرنببل‪ ،‬توسبعه دهنببدگان بببه ایبن نببتیجه‬
‫رسیداندکه فایلهای دستگاه‪ ،‬بیشتر به فضای کاربر نزدیک است تا به فضای کرنل ‪.‬به همین دلیببل در کرنلهببای‬
‫یشود و بقیه کار از جمله تفسببیر اطلعببا ت و‬
‫امروزی فقط کلس دستگاه و اطلعا ت دستگاه در ‪ /sys‬انجام م د‌‬
‫انجام رفتار مناسب در فضای کاربر انجام میگیببرد‪.‬در اکبثر لینوکسبهای رومیببزی‪،‬دیمبان ‪ udev‬اطلعبا ت را‬
‫ل دستگاه را میسازد‪ .‬همچنین ‪ udev‬بوسیلد‌هی فایل پیکربنببدی میتوانببد نببام فایببل‬
‫میگیرد و بر اساس آن فای ِ‬
‫دستگاه ‪ ،‬دسترسی ها و انواع آن و‪ ...‬را تعیین نماید‪ .‬درایور‪ ،‬لزم است تا اطلعا ت و ورودیهای مناسببب ‪/sys‬‬
‫را با استفاده از ‪ API‬هایی که در هدر فایل ‪ /linux/device.h‬موجود اسببت را پببر نمایببد و بقید‌هی کارهببا‬
‫توسط ‪ udev‬انجام میگیرد‪.‬کلس دستگاه بصور ت زیر ایجاد میگردد‪:‬‬

‫;)">‪struct class *cl = class_create(THIS_MODULE, "<device class name‬‬

‫سپس اطلعا ت دستگاه )‪ (major,minor‬تحت این کلس بوسیله کد زیر پر میشود‪:‬‬

‫;)‪device_create(cl, NULL, first, NULL, "<device name format>", ...‬‬

‫‪ dev_t‬با >‪ < major,minor‬مرتبط است و برای آزادسببازی و تخریببب آن نیببز بصببور ت معکببوس بایببد‬
‫بصور ت زیر انجام شود‪:‬‬

‫;)‪device_destroy(cl, first‬‬

‫صفحه‪24‬‬
‫;)‪class_destroy(cl‬‬

‫شکل زیر ورودی ایجاد شده در ‪ /sys‬توسببط ‪ chardrv‬بعنببوان یببک > ‪ <device class name‬و‬
‫ل دسببتگا ِه سبباخته‬
‫‪ mynull‬را بعنوان یک >‪ <device name format‬مشاهده میکنیم‪ .‬همچنین فای ِ‬
‫شده در ‪ /dev‬را با استفاده از ‪ udev‬میتوانیم مشاهده کنیم‪.‬‬

‫نکته جالب توجه این است که اگر ما چندین شماره ‪ minor‬داشته باشیم ‪ API‬هببای )(‪device_create‬‬
‫و )(‪ device_destroy‬را با استفاده از >‪ <device name format‬میتوانیم در یک حلقه ‪ for‬قرار‬
‫دهیم‪.‬بعنوان مثال )(‪ device_create‬را با ایندکس ‪ i‬را میتوانیم به شکل زیر فراخوانی کنیم‪:‬‬

‫;)‪device_create(cl, NULL, MKNOD(MAJOR(first), MINOR(first) + i), NULL, "mynull%d", i‬‬

‫صفحه‪25‬‬
‫ل دستگاه‬
‫عملیات بروی فای ِ‬
‫تمام عملیا ت و سیستم کالهایی که جهت کار با فایل معمولی استفاده میگردد‪،‬برای فایلهببای دسببتگاه نیببز‬
‫نهببا در کرنببل‬
‫قابل استفاده میباشد‪.‬در لینوکس اغلب موارد از دیدکاربر تنها یک فایل میباشد و تنها تفبباو ت آ د‌‬
‫میباشد که از دید کاربر پنهان است‪.‬فایل سیستم مجازی ‪ VFS‬لینوکس انواع فایلها را تشخیص داده و عملیا ت‬
‫ل بببرای شبباخد‌هها و فایلهببای‬
‫فایل مرتبط با هر نوع فایل را به کانال مخصوص به آن فایببل ارجبباع میدهببد‪ .‬مث ا ً‬
‫معمولی به ماژولهای فایل سیستم ارجاع میدهد و برای فایلهای دستگاه کاراکتری به دیوایس درایور مرتبببط بببا‬
‫آن ارجاع میدهد‪.‬که ما در این اینجا فقط در مورد مکانیزم فایلهای کاراکتری بحث میکنیم‪.‬‬
‫پس ما باید در مورد ‪ VFS‬که وظیفه دارد هر عملیا ت فایل را به درایببور مربببوطه منتقببل کنببد‪ ،‬بیشببتر‬
‫بدانیم‪.‬کاربا ‪ VFS‬دو گام دارد )کدهای داخل پرانتز برای درایور »تهی« میباشد که بعدا ًا توضیح خواهیم داد(‪:‬‬
‫گام اول ‪ ،‬پرکردن استراکچر عملیا ت فایل )‪ (struct file_operaton pugs_fops‬با تببوابعی کببه‬
‫ببببببببببرای آن عملیبببببببببا ت دلخبببببببببواه نوشبببببببببته ایبببببببببم )تبببببببببوابعی ماننبببببببببد‬
‫‪(...,my_open,my_close,my_read,my_write‬و مقدار دهی اولیه اسببتراکچر فایببل کبباراکتری )‬
‫‪ (struct cdev c_dev‬با استفاده از تابع )(‪. cdev_init‬‬
‫گام دوم معرفی این ساختار به ‪ VFS‬میباشدکه با استفاده از )(‪ cdev_add‬انجام میگیرد‪.‬لزم به ذکر اسببت‬
‫که )(‪ cdev_init‬و )(‪ cdev_add‬هر دو در هدر فایل >‪ <linux/cdev.h‬تعریف شده اند‪.‬‬
‫برای شروع تا جاییکه ممکن است کار را ساده میگیریم‪ .‬بنابراین میخواهیم در ابتدای کار درایور »تهی« ) ‪(null‬‬
‫را بنویسیم‪.‬‬

‫درایور تهی‬
‫اولین درایورکامل کاراکتری خود را بصور ت زیر مینویسیم و اسم آنرا ‪ ofcd.c‬میگذاریم‪:‬‬

‫>‪#include <linux/module.h‬‬
‫>‪#include <linux/version.h‬‬
‫>‪#include <linux/kernel.h‬‬
‫>‪#include <linux/types.h‬‬
‫>‪#include <linux/kdev_t.h‬‬
‫>‪#include <linux/fs.h‬‬
‫>‪#include <linux/device.h‬‬
‫>‪#include <linux/cdev.h‬‬

‫صفحه‪26‬‬
static dev_t first; // Global variable for the first device number
static struct cdev c_dev; // Global variable for the character device structure
static struct class *cl; // Global variable for the device class
static int my_open(struct inode *i, struct file *f)
{
printk(KERN_INFO "Driver: open()\n");
return 0;
}
static int my_close(struct inode *i, struct file *f)
{
printk(KERN_INFO "Driver: close()\n");
return 0;
}
static ssize_t my_read(struct file *f, char __user *buf, size_t
len, loff_t *off)
{
printk(KERN_INFO "Driver: read()\n");
return 0;
}
static ssize_t my_write(struct file *f, const char __user *buf,
size_t len, loff_t *off)
{
printk(KERN_INFO "Driver: write()\n");
return len;
}
static struct file_operations pugs_fops =
{
.owner = THIS_MODULE,
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write
};

27‫صفحه‬
static int __init ofcd_init(void) /* Constructor */
{
printk(KERN_INFO "Namaskar: ofcd registered");
if (alloc_chrdev_region(&first, 0, 1, "Shweta") < 0)
{
return -1;
}
if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)
{
unregister_chrdev_region(first, 1);
return -1;
}
if (device_create(cl, NULL, first, NULL, "mynull") == NULL)
{
class_destroy(cl);
unregister_chrdev_region(first, 1);
return -1;
}
cdev_init(&c_dev, &pugs_fops);
if (cdev_add(&c_dev, first, 1) == -1)
{
device_destroy(cl, first);
class_destroy(cl);
unregister_chrdev_region(first, 1);
return -1;
}
return 0;
}

static void __exit ofcd_exit(void) /* Destructor */


{
cdev_del(&c_dev);
device_destroy(cl, first);
class_destroy(cl);

28‫صفحه‬
‫;)‪unregister_chrdev_region(first, 1‬‬
‫;)"‪printk(KERN_INFO "Alvida: ofcd unregistered‬‬
‫}‬

‫;)‪module_init(ofcd_init‬‬
‫;)‪module_exit(ofcd_exit‬‬
‫;)"‪MODULE_LICENSE("GPL‬‬
‫;)">‪MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com‬‬
‫;)"‪MODULE_DESCRIPTION("Our First Character Driver‬‬

‫بعد از نوشتن و ذخیره برنامه فوق میتوانیم کارهای زیر را بروی درایور نوشته شده انجام دهیم‪:‬‬
‫• ساخت درایور )فایل ‪ (.ko‬با استفاده از دستور ‪make‬‬
‫• لود کردن درایور در حافظه با استفاده از دستور ‪insmod‬‬
‫• مشاهده درایورهای بارگذاری شده با استفاده از دستور ‪lsmode‬‬
‫• مشاهده شمارد‌ههای اصلی و فرعی اختصاص داده شده به درایو با استفاده از دستور‬
‫‪cat /proc/devices‬‬
‫• امتحان کردن خصوصیا ت درایور تهی با کمک گرفتن از شکل زیر‬
‫• حذف درایور از کرنل با استفاده از ‪rmmod‬‬

‫صفحه‪29‬‬
‫بعد از طی این مراحل خوشحالیم که توانستیم اولین درایور خود را نویسیم‪ .‬قابل ذکر است که این درایور‬
‫ل استاندار ِد ‪ / dev/null‬کار میکند‪.‬برای اینکه معنببی ایببن حببرف را در ک کنیببد کببافی اسببت‬
‫ما شبیه به فای ِ‬
‫شمارد‌ههای اصلی و فرعی فایل ‪ / dev/null‬را مشاهده کنیبد و سبعی کنیبد ببا اسبتفاده از دسبتورا ت ‪ cat‬و‬
‫‪ echo‬در این فایل بنویسید یا بخوانید‪ .‬رفتار این فایل با درایوری که ما نوشتد‌هایم دقیقا ًا خواهد بود‪.‬‬
‫ولبببی شببباید یبببک سبببؤال ببببرای شبببما هنبببوز مطبببرح باشبببد و آن ایبببن اسبببت کبببه مبببا تواببببع‬
‫‪my_read,my_write,my_open,my_close‬و‪ ...‬را فراخوانی کردیم ولببی اگببر بخببواهیم بصببور ت‬
‫واقعی عملیا ت خواندن یا نوشتن را داشته باشیم چگونه توسط این توابع اینکار را انجام دهیم؟ جببواب بببه ایببن‬
‫پرسش در فصل بعد بررسی خواهد شد‪.‬‬

‫صفحه‪30‬‬
‫فصل ششم ‪ :‬خواندن و نوشتن بروی فایل دستگاههای‬
‫کاراکتری‬

‫نپذیر نبود‪ .‬در این فصل میخواهیم‬


‫در فصل قبل خواندن و نوشتن واقعی بروی فایلهای کاراکتری امکا د‌‬
‫این مهم را انجام دهیم‪.‬برای این کار بیایید دوباره به بخشهایی از کد نوشته شده در فصل قبل نگاهی بیاندازیم‪:‬‬

‫)‪static int my_open(struct inode *i, struct file *f‬‬


‫{‬
‫;)"‪printk(KERN_INFO "Driver: open()\n‬‬
‫;‪return 0‬‬
‫}‬
‫)‪static int my_close(struct inode *i, struct file *f‬‬
‫{‬
‫;)"‪printk(KERN_INFO "Driver: close()\n‬‬
‫;‪return 0‬‬
‫}‬
‫‪static ssize_t my_read(struct file *f, char __user *buf, size_t len,‬‬
‫)‪loff_t *off‬‬
‫{‬
‫;)"‪printk(KERN_INFO "Driver: read()\n‬‬
‫;‪return 0‬‬
‫}‬
‫‪static ssize_t my_write(struct file *f, const char __user *buf,‬‬
‫)‪size_t len, loff_t *off‬‬
‫{‬
‫;)"‪printk(KERN_INFO "Driver: write()\n‬‬
‫;‪return len‬‬
‫}‬

‫یبینیم که نوع داده بازگشتی توابببع )(‪ my_open‬و )(‪ my_close‬از نببوع ‪Int‬‬
‫با توجه به کدهای فوق م د‌‬
‫میباشد ولی مقدار صفر )اطلعا ت ناچیز( را بازگشت میدهند که آن هم به معنی موفقیت آمیز بببودن عملیببا ت‬
‫این توابع است‪.‬در مقابل نوع داده بازگشتی توابع )(‪ my_read‬و )(‪ my_write‬از نوع ‪ int‬نبوده و مطببابق‬
‫کد فوق از نوع ‪ ssize_t‬میباشد که با کاوش در میان هدر فایلهای کرنل اطلعببا ت خببوبی را در ایببن مببورد‬

‫صفحه‪31‬‬
‫میتوانیم پیدا کنیم‪.‬بازگرداندن عدد منفی معموا ًل نشانگر یک خطا است در صورتی کببه بازگردانببدن یببک عببدد‬
‫مثبت اطلعا ت بیشتری را به ما میدهد‪.‬برای خواندن از فایل ‪ ،‬مقدار بازگشتی باید با تعداد بایتهای خوانده شده‬
‫برابر باشد و همچنین برای نوشتن ‪ ،‬مقدار بازگشتی میبایست تعداد بایتهای نوشته شده را نشان دهد‪.‬‬
‫چگونگی خواندن از فایل دستگاه‬
‫با دانستن مفاهیم فوق اگر دوباره به کد نوشته شده نگاهی بیاندازیم متوجه خواهیم شدکه در عملیببا ت‬
‫ل دستگا ِه ‪ /dev/mynull‬میخواند سیستم کال آنرا به فایل سیستم ‪ VFS‬در لیه‬
‫خواندن‪ ،‬وقتی کاربر از فای ِ‬
‫یشببود کببه بایببد تببابع )(‬
‫یآورد و متوجه م د‌‬
‫کرنل میرساند‪.‬سپس ‪ VFS‬زوج >‪ < major,minor‬را بدست م د‌‬
‫ل ثبت شده است را فراخوانی کند‪.‬از اینرو تابع )(‪ my_read‬که نویسندد‌هی درایببور آنببرا‬
‫‪ ‌my_read‬که قب ا ً‬
‫ت خوانببدن را بازگشببت‬
‫نوشته است بدینگونه فراخوانی میشود‪.‬این تابع باید مقدار خوانده شده در هر درخواس ب ِ‬
‫دهد‪.‬درکد قبل ما مقدار صفر را بازگشت دادیم که بدین معنی است که مقببداری خوانببده نشببده اسببت یببا بببه‬
‫عبار ت دیگر در آخر فایل هستیم‪ .‬از اینرو وقتی فایل دستگاه شروع به خواندن میکند همواره تهببی بازگردانببده‬
‫میشود‪.‬‬
‫سؤالی که ممکن است مطرح شود این است که اگر ما بجای بازگرداندن عدد صفر ‪ ،‬عدد یک را بازگردانیم آیببا‬
‫یشویم که جواب سؤال فوق مثبت اسببت ولببی در‬
‫دادد‌های را کسب میکنیم؟ با نگاه به پارامترهای تابع متوجه م د‌‬
‫نظر داشته باشیم که دادد‌ههای بازگشتی دادد‌ههای آشغال و تصادفی هسببتند بببدلیل اینکببه تببابع )(‪my_read‬‬
‫مقدار متغیر ‪ buf‬را دستکاری نمیکند‪.‬توجه کنید که ‪ buf‬دومیببن پببارامتر تببابع )(‪ my_read‬میباشببد کببه‬
‫توسط کاربر ایجاد میشود‪.‬‬
‫درحقیقت برای عملکرد منطقی و درست تابع خواندن در فایلهای دسببتگاد‌ههای کبباراکتری )کببه در اینجببا تببابع‬
‫‪ my_read‬میباشد(‪،‬میبایست این تابع منطبق بر ورودی ‪ len‬که سومین پارامتر تابع خواندن است و تعببداد‬
‫بایتهای درخواست شده را نشان میدهد‪،‬به همان میزان دادد‌هها را در ‪ buf‬بریزد‪.‬بببه عبببار ت دیگببر بایببد ‪buf‬‬
‫مقدار مساوی یا کمتر از ‪ len‬بایت داده داشته باشد و تعداد بایتهای خوانده شده باید توسط تببابع بازگردانببده‬
‫نتر دادد‌هها را میخواند و در بافر قرار میدهد و آنگاه کاربر میتواند داده‬
‫شود‪.‬بدین گونه نویسنده تابع در لیه پایی د‌‬
‫ها را در لیه بالتر بخواند‪.‬‬

‫چگونگی نوشتن در فایل دستگاه‬


‫ل دستگاه دقیقا ًا برعکس عملیا ت خواندن میباشد‪.‬بدین ترتیب که کاربر تعداد ‪ len‬بببایت‬
‫عملیا ت نوشتن در فای ِ‬
‫)سومین پارامتر تابع )(‪ ( my_write‬که میبایسببت نوشببته شببود را در بببافر ‪) buf‬دومیببن پببارامتر تببابع )(‬
‫‪ ( my_write‬قرار میدهد سپس تابع )(‪ my_write‬این دادد‌هها را خوانده و در صور ت امکببان در دسببتگاه‬
‫لیه زیرین بنویسد و تعداد بایتی را که با موفقیت نوشته است را بازگردانببد‪.‬بببه همیببن دلیببل اسببت کببه تمببام‬

‫صفحه‪32‬‬
‫درخواستهای خواندن و نوشتن ما بروی دستگاه ‪ /dev/mynull‬بدون اینکه خواندن یا نوشتنی در کار باشد‪،‬‬
‫موفقیت آمیز بوده است‪.‬‬

‫صفحه‪33‬‬
‫برنامه ثبت آخرین کاراکتر نوشته شده‬
‫حال میخواهیم با دانستد‌ههای فوق تابع )(‪ my_read‬و )(‪ my_write‬را به نحوی تغییر دهیم تا با‬
‫هر بار خواندن ‪ ،‬آخرین کاراکتر نوشته را بازگرداند‪.‬‬
‫به کد زیر توجه نمایید‪:‬‬

‫;‪static char c‬‬

‫)‪static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off‬‬
‫{‬
‫;)"‪printk(KERN_INFO "Driver: read()\n‬‬
‫;‪buf[0] = c‬‬
‫;‪return 1‬‬
‫}‬
‫)‪static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off‬‬
‫{‬
‫;)"‪printk(KERN_INFO "Driver: write()\n‬‬
‫;]‪c = buf[len – 1‬‬
‫;‪return len‬‬
‫}‬

‫کد فوق به درستی کار میکند ولی فقط یک مشکل ممکن است وجود داشته باشد و آن این است که اگر کبباربر‬
‫بافر غیر معتبری را پر کند آنگاه ما با یک خطای کرنل مواجه خواهیم شببد‪ .‬بببرای رفببع ایببن مشببکل دو ‪API‬‬
‫معرفی شده است تا بافرهای یوزر را برسی کندکه امن و صحیح هستند یا خیر‪ .‬پس بیایید با استفاده از ایببن دو‬
‫‪ API‬دوباره کد را بازنویسی کنیم‪:‬‬
‫قابل ذکر است که این دو تابع به هدر فایل >‪ <asm/uaccess.h‬نیاز دارند‪.‬‬

‫;‪static char c‬‬

‫)‪static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off‬‬
‫{‬
‫;)"‪printk(KERN_INFO "Driver: read()\n‬‬
‫)‪if (copy_to_user(buf, &c, 1) != 0‬‬

‫صفحه‪34‬‬
‫;‪return -EFAULT‬‬
‫‪else‬‬
‫;‪return 1‬‬
‫}‬
‫)‪static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off‬‬
‫{‬
‫;)"‪printk(KERN_INFO "Driver: write()\n‬‬
‫)‪if (copy_from_user(&c, buf + len – 1, 1) != 0‬‬
‫;‪return -EFAULT‬‬
‫‪else‬‬
‫;‪return len‬‬
‫}‬

‫برای ساخت درایورمان دوباره مراحل زیر را انجام میدهیم‪:‬‬


‫• ساخت درایور با استفاده از تغییراتی که انجام دادد‌هایم و بوسیله دستور ‪make‬‬
‫• لود کردن درایور با استفاده از دستور ‪insmod‬‬
‫• نوشتن در فایل ‪ /dev/mynull‬با استفاده از دستور ‪echo -n “Pugs” < /dev/mynull‬‬
‫• خواندن از فایل فوق بببا اسببتفاده از دسببتور ‪ ) cat /dev/mynull‬بببا اسببتفاده از ‪ ctrl+c‬اتمببام‬
‫میشود(‬
‫• حذف درایور از کرنل با استفاده از دستور ‪rmmod‬‬

‫یشببود کببه‬
‫با اجرای دستور ‪ cat /dev/mynull‬بدون وقفه ‪ ،‬آخرین کاراکتر نوشته شده بازگشت داده م د‌‬
‫برای خروج از این حالت میبایست ‪ ctrl+c‬فشرده شود‪.‬تابع )(‪ my_read‬فقط یکبار عببدد ‪ ۱‬برمیگردانببد و‬
‫برای دفعا ت بعد میبایست عدد صفر بازگردانده شود‪ .‬این مورد با استفاده از پارامتر چهارم تابع )متغییببر ‪(off‬‬
‫قابل انجام میباشد‪.‬‬

‫صفحه‪35‬‬
‫فصل هفتم ‪ :‬دسترسی به سخت افزار در لینوکس‬

‫تافزار‬
‫رابط عمومی سخ ه‌‬
‫تافزار را تعریف کنیم‪.‬فعل کاری به جزییببا ت‬
‫برای در ک بهتر این فصل نخست باید رابط عمومی سخ د‌‬
‫تافزار را نشان میدهد‪ .‬ایبن راببط مببوجب عببدم وابسببتگی ببه‬
‫تئوریک آن نداریم‪.‬عکس زیر رابط عمومی سخ د‌‬
‫ل معماری ماشین را پنهان میکند‪.‬‬
‫یشود و عم ا ً‬
‫تافزار م د‌‬
‫معماری سخ د‌‬

‫بدلیل اینکه برای معماری های مختلف شکل فوق متفاو ت میباشد‪ ،‬فرض بر این است که ما در مورد معمبباری‬
‫‪ ۳۲‬بیتی بحث میکنیم‪ .‬برای آدرس باس ‪ ۳۲‬بیتی‪ ،‬حافظه از )‪ (0x00000000‬شروع شده و تا )‪(0xffffffff‬‬
‫ییابد‪.‬برای عدم وابستگی به معماری باید طرح ما چیزی شبیه به شکل فوق باشد‪.‬جاییکه حافظه ‪RAM‬‬
‫ادامه م د‌‬
‫و ناحیه دستگاهها در هم تنیده شده باشند‪.‬برای معماری ‪ ۳۲‬بیتی معموا ًل ‪ ۳‬گیگابایت برای ‪ RAM‬آدرس دهی‬
‫یشود و یک گیگابایت باقیمانده کببه رنببج‬
‫یشوند که رنج آدرس ‪ 0x00000000‬تا ‪ 0xbfffffff‬را شامل م د‌‬
‫م د‌‬
‫یشود به ناحیه دستگاهها اختصبباص مببی یابببد‪.‬اگببر حببافظه‬
‫آدرس ‪ 0xc0000000‬تا ‪ 0xffffffff‬را شامل م د‌‬
‫ل ‪ ۲‬گیگابببایت( آنگبباه نبباحیه دسببتگاه میتوانببد از آدرس ‪0x80000000‬‬
‫دستگاه کمتر از ‪ ۴‬گیگابایت باشد)مث ا ً‬
‫شروع شود‪.‬‬

‫صفحه‪36‬‬
‫برای مشباهده ترتیبب نگاشبت حبافظه )یبا همبان آدرس دهبی( در سیسبتم خبود میتوانیببد از دسبتور ‪cat‬‬
‫‪ /proc/iomem‬استفاده نمایید‪.‬همچنین برای مشاهده اندازه تقریبی حافظه ‪ RAM‬سیستم خود میتوانید از‬
‫دستور ‪ cat /proc/meminfo‬کمک بگیرید‪.‬در شکل زیر خروجی این دو دستور را در سیستم من نشان‬
‫میدهد‪:‬‬

‫قابل ذکر است که مقادیری که به حافظه ‪ RAM‬اشاره میکند را آدرس فیزیکی و مقادیری کببه بببه دسببتگاهها‬
‫اشاره میکنند را آدرس باس گویند‪.‬دلیل نامگذاری آدرس باس به این دلیل است که دستگاهها همیشه فارغ از‬
‫نوع معماری به باس آن سیسبتم متصببل میگردنبد‪.‬ماننببد ‪ PCI‬در معمباری ‪ x86‬یبا ‪ AMBA‬در معمباری‬
‫‪ ARM‬و یا ‪ SuperHyway‬در معماری ‪ SuperH‬و‪...‬‬
‫یشببوند ولببی در‬
‫سهای فیزیکی و باس به دوشیوه داینامیک و یا از طریق معماری پردازشببگر پیکربنببدی م د‌‬
‫آدر د‌‬
‫لینوکس به دلیل اینکه اجازه دسترسی بصور ت مستقیم به حافظه وجود ندارد میبایست ابتدا به حافظه مجببازی‬
‫نگاشت شود و سپس از طریق آن به حافظه ‪ RAM‬یا حافظه دستگاه دسترسی حاصبل شببود‪ .‬دو ‪ API‬مرتببط‬
‫جهت نگاشت یا عدم نگاشت آدرس حافظه دستگاه به حافظه مجازی به شرح زیر میباشد‪.‬نکته قابل ذکببر ایببن‬
‫است که این دو ‪ API‬در هدر فایل >‪ <asm/io.h‬تعریف شده است‪:‬‬

‫صفحه‪37‬‬
‫‪void‬‬ ‫‪*ioremap(unsigned‬‬ ‫‪long‬‬ ‫‪device_bus_address,‬‬ ‫‪unsigned‬‬ ‫‪long‬‬
‫;)‪device_region_size‬‬
‫;)‪void iounmap(void *virt_addr‬‬

‫برای خواندن و نوشتن در حافظه نگاشت شده توابع زیر که همگی نیز در هببدر فایببل >‪ <asm/io.h‬تعریببف‬
‫شدد‌هاند در دسترس میباشد‪:‬‬

‫;)‪unsigned int ioread8(void *virt_addr‬‬


‫;)‪unsigned int ioread16(void *virt_addr‬‬
‫;)‪unsigned int ioread32(void *virt_addr‬‬
‫;)‪unsigned int iowrite8(u8 value, void *virt_addr‬‬
‫;)‪unsigned int iowrite16(u16 value, void *virt_addr‬‬
‫;)‪unsigned int iowrite32(u32 value, void *virt_addr‬‬

‫یادی از سیستم عامل قدیمی ‪:DOS‬‬


‫اگر با سیستم عامل قدیمی ‪ DOS‬کار کرده باشید در آن موقع میتوانستید بصور ت آزادنببه بببه حببافظه‬
‫ویدئو )گرافیک( دسترسی داشته باشید‪ .‬الن هم با اطلعاتی که در دست داریم میتوانیم مشابه آن کار را انجام‬
‫دهیم یعنی میخواهیم به حافظه گرافیک دسترسی داشته باشیم‪ .‬برای اینکار با استفاده از مقا دیر موجود در فایل‬
‫‪ / proc/iomem‬رنج آدرس حافظه گرافیک سیستم خود را بدست آوریببد‪ .‬بببرای سیسببتم مببن ایببن رنببج‬
‫آدرس بین ‪ 0x000a0000‬تا ‪ 0x000bffff‬میباشد‪.‬ما میخواهیم از ‪ API‬های فوق استفاده کرده و پارامترها‬
‫ل نوشته بودیم اضببافه کنیببم و آنببرا درایببور ‪vram‬‬
‫را به توابع سازنده و تخریب گر برنامه درایور تهی که قب ا ً‬
‫ینامیم سپس با استفاده از خواندن و نوشتن در سطح یوزر و با استفاده از درایور ‪ vram‬میتوانیم به حافظد‌هی‬
‫م د‌‬
‫گرافیک دسترسی داشته باشیم ‪.‬بنابراین کد جدید ما که با نام ‪ video_ram.c‬ذخیره میکنیم شبیه کد زیببر‬
‫خواهد بود‪:‬‬

‫>‪#include <linux/module.h‬‬
‫>‪#include <linux/version.h‬‬
‫>‪#include <linux/kernel.h‬‬
‫>‪#include <linux/types.h‬‬
‫>‪#include <linux/kdev_t.h‬‬
‫>‪#include <linux/fs.h‬‬

‫صفحه‪38‬‬
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <asm/io.h>

#define VRAM_BASE 0x000A0000


#define VRAM_SIZE 0x00020000

static void __iomem *vram;


static dev_t first;
static struct cdev c_dev;
static struct class *cl;

static int my_open(struct inode *i, struct file *f)


{
return 0;
}
static int my_close(struct inode *i, struct file *f)
{
return 0;
}
static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off)
{
int i;
u8 byte;

if (*off >= VRAM_SIZE)


{
return 0;
}
if (*off + len > VRAM_SIZE)
{
len = VRAM_SIZE - *off;
}

39‫صفحه‬
for (i = 0; i < len; i++)
{
byte = ioread8((u8 *)vram + *off + i);
if (copy_to_user(buf + i, &byte, 1))
{
return -EFAULT;
}
}
*off += len;

return len;
}
static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off)
{
int i;
u8 byte;

if (*off >= VRAM_SIZE)


{
return 0;
}
if (*off + len > VRAM_SIZE)
{
len = VRAM_SIZE - *off;
}
for (i = 0; i < len; i++)
{
if (copy_from_user(&byte, buf + i, 1))
{
return -EFAULT;
}
iowrite8(byte, (u8 *)vram + *off + i);
}
*off += len;

40‫صفحه‬
return len;
}

static struct file_operations vram_fops =


{
.owner = THIS_MODULE,
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write
};

static int __init vram_init(void) /* Constructor */


{
if ((vram = ioremap(VRAM_BASE, VRAM_SIZE)) == NULL)
{
printk(KERN_ERR "Mapping video RAM failed\n");
return -1;
}
if (alloc_chrdev_region(&first, 0, 1, "vram") < 0)
{
return -1;
}
if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL)
{
unregister_chrdev_region(first, 1);
return -1;
}
if (device_create(cl, NULL, first, NULL, "vram") == NULL)
{
class_destroy(cl);
unregister_chrdev_region(first, 1);
return -1;

41‫صفحه‬
}

cdev_init(&c_dev, &vram_fops);
if (cdev_add(&c_dev, first, 1) == -1)
{
device_destroy(cl, first);
class_destroy(cl);
unregister_chrdev_region(first, 1);
return -1;
}
return 0;
}

static void __exit vram_exit(void) /* Destructor */


{
cdev_del(&c_dev);
device_destroy(cl, first);
class_destroy(cl);
unregister_chrdev_region(first, 1);
iounmap(vram);
}

module_init(vram_init);
module_exit(vram_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>");
MODULE_DESCRIPTION("Video RAM Driver");

:‫پس از ثبت کد فوق طبق معمول کارهای زیر را برای اجرای درایورمان انجام میدهیم‬
(video_ram.ko ‫ میسازیم )فایل‬make ‫ را با استفاده از دستور‬vram ‫• درایور‬
insmod ‫• لود کردن درایور با استفاده از دستور‬

42‫صفحه‬
‫• با استفاده از دستور ‪ echo -n “0123456789” > /dev/vram‬در فایل ‪/dev/vram‬‬
‫مینویسیم‬
‫• با استفاده از دستور ‪ od -t x1 -v /dev/vram | less‬از فایل فوق میخوانیم ‪ .‬ما همچنین‬
‫میتوانیم از دستور ‪ cat‬نیز استفاده نماییم ولی مقادیر بازگشت داده شده بصور ت باینری خواهند بود‬
‫در حالی که دستور ‪ od -t x1‬مقادیر را بصور ت هگزادسیمال نشان میدهد‪.‬‬
‫• درایور را با استفاده از ‪ rmmod video_ram‬از حافظه خارج میکنیم‪.‬‬

‫صفحه‪43‬‬
‫فصل هشتم ‪ :‬دسترسی به پور ت ورودی‪/‬خروجی خاص‬
‫در معماری ‪x86‬‬

‫رابط سخت افزاری در معماری ‪x86‬‬


‫تافزار ‪ ،‬بجببای نگاشببت حببافظه‬
‫برخلف معماری های دیگر در معماری ‪ x86‬برای دسترسببی بببه سببخ د‌‬
‫دستگاه به حافظه مجازی‪ ،‬یک مکانیزم دیگری در نظبر گرفتببه شببده اسبت و آن یبک آدرس دهبی ‪ ۱۶‬بیببتی‬
‫مستقیم بوده که به آن آدرس پور ت نیز گفته میشود‪.‬از آنجاییکه این مکانیزم یک روش اضافه تببر نسبببت بببه‬
‫معماری های دیگر میباشد‪،‬برای کار با آن نیز دستورا ت اضافی )کببدهای اسببمبلی‪ /‬کببد ماشببین( آمبباده شببده‬
‫است‪.‬برای مثال دستورا ت ‪ inb‬و ‪ inw‬و ‪ inl‬به ترتیب جهت خواندن ‪ ۸‬بیت )یببک بببایت( ‪ ۱۶ ،‬بیببت )یببک‬
‫کلمه( و ‪ ۳۲‬بیت )یک کلمه بزرگ( از ورودی یک دستگاه نگاشت شده و متناظرا ًا دسببتورا ت ‪ outb‬و ‪outw‬‬
‫و ‪ outl‬جهت نوشتن در یک دستگاه نگاشت شده از طریق پور ت یا پورتها در دسترس میباشببد‪.‬لزم بببه ذکببر‬
‫است که دستورا ت فوق بصور ت زیر و در هدر فایل >‪ <asm/io.h‬تعریف گردیده اند ‪:‬‬

‫‪;(u8 inb(unsigned long port‬‬


‫;)‪u16 inw(unsigned long port‬‬
‫;)‪u32 inl(unsigned long port‬‬
‫;)‪void outb(u8 value, unsigned long port‬‬
‫;)‪void outw(u16 value, unsigned long port‬‬
‫;)‪void outl(u32 value, unsigned long port‬‬

‫شاید برای شما سؤالی مطرح شود که کدام دستگاه ورودی‪/‬خروجی و کدام پور ت مببورد نظببر میباشببد؟‬
‫پاسخ به این پرسش ها ساده است بدلیل اینکه در معماری ‪ x86‬هر دستگاه استاندارد از قبل محل نگاشببت آن‬
‫تعریف گردیده است‪.‬با استفاده از خروجی فایل ‪ /proc/ioports‬میتببوانیم ایببن نگاشببت هببا کببه در کرنببل‬
‫تعریف گردیده است را در سیستم خببود مشبباهده نمبباییم‪.‬ایببن دسببتگاد‌هها عبارتنببد از تببایمر ‪، DMA ،‬درگبباه‬
‫ینمایید‪:‬‬
‫سریال‪،‬درگاه موازی ‪ ، RTC ،‬رابط باس ‪ PCI‬وغیره میباشدکه در شکل زیر مشاهده م د‌‬

‫صفحه‪44‬‬
‫دسترسی به پورت سریال در ‪x86‬‬
‫ل ذکر شد هر دستگاه در معماری ‪ x86‬از قبل به حافظه نگاشت شده اسببت‪.‬بببرای مثببال‬
‫همانطور که قب ا ً‬
‫ل بببه چببه‬
‫اولین پور ت سریال ‪،‬همیشه در آدرس ‪ 0x3F8‬تا ‪ 0x3FF‬نگاشت شده است‪.‬ولی ایببن نگاشببت عم ا ً‬
‫معنی است و این اطلعا ت چه کمکی به ما جهت دسترسی به پور ت سریال میکند و ما چه کارهایی را میتببوانیم‬
‫انجام دهیم؟‬
‫یشببود کببه بببه‬
‫همانگونه که در فصل اول اشاره شد هر درگاه سریال توسط یک دیوایس کنترلر سریال‪،‬کنترل م د‌‬
‫آن ) ‪ (Universal Asynchronous Receiver/Transmitter‬یا ‪ UART‬گوینببد یببا مث ا ً‬
‫ل‬
‫برای تایمر‪ ،‬ما ) ‪(Universal Synchronous/Asynchronous Receiver/Transmitte‬‬
‫یا ‪USART‬د‌ را داریم‪.‬در یک کامپیوتر معمولی کنترلر ‪ UART‬از ‪ IC‬مدل ‪ PC16550D‬استفاده میکنببد کببه‬
‫مشخصا ت آنرا میتوانید از نشانی های زیر دریافت نمایید‪:‬‬

‫‪http://www.national.com/ds/PC/PC16550D.pdf‬‬
‫‪http://esrijan.com/DDK/LDDK-Package‬‬
‫‪http://esrijan.com/index.php?pagefile=lddk‬‬

‫صفحه‪45‬‬
‫سها میتوانیم از ماکروهایی که تعریف شده است استفاده نمبباییم‪ .‬مثلا ً بببرای پببور ت‬
‫بجای هارد کد کردن آدر د‌‬
‫ل‪:‬‬
‫سریال میتوانم از هدر فایل >‪ <linux/serial_reg.h‬استفاده کنیم مث ا ً‬

‫‪#define SERIAL_PORT_BASE 0x3F8‬‬

‫ل برای خواندن ونوشتن بروی پور ت سریال از کد زیر استفاده میشود‪:‬‬


‫مث ا ً‬

‫;‪u8 val‬‬
‫;)‪val = inb(SERIAL_PORT_BASE + UART_LCR /* 3 */‬‬
‫;)‪outb(val, SERIAL_PORT_BASE + UART_LCR /* 3 */‬‬

‫که منظور از ‪ LCR‬همان ‪ Line Control Register‬میباشد‪.‬‬


‫و برای یک یا صفر کردن تمام بیتها میتوان از کد زیر استفاده کرد‪:‬‬

‫;‪u8 val‬‬
‫;)‪val = inb(SERIAL_PORT_BASE + UART_LCR /* 3 */‬‬

‫‪/* Setting DLAB */‬‬


‫;‪val |= UART_LCR_DLAB /* 0x80 */‬‬
‫;)‪outb(val, SERIAL_PORT_BASE + UART_LCR /* 3 */‬‬

‫‪/* Clearing DLAB */‬‬


‫;‪val &= ~UART_LCR_DLAB /* 0x80 */‬‬
‫;)‪outb(val, SERIAL_PORT_BASE + UART_LCR /* 3 */‬‬

‫که منظور از ‪ DLAB‬همان ‪ Divisor Latch Access Bit‬میباشد‪.‬‬

‫ساخت یک ‪ LED‬واقعی‬
‫ت درایببو ِر دسببتگاه‬
‫تافزار و درایورها در لینوکس ساخت یببک کی ب ِ‬
‫یک مثال عملی برای دستیابی به سخ د‌‬
‫لینوکس)‪ (LDDK‬میباشد‪.‬سپس میخواهیم یک ‪LED‬د‌ را بصور ت چشمک زن درآوریم‪.‬برای اینکار یک ‪‌LED‬‬
‫با مقاومت ‪ ۳۳۰‬اهم را به پاید‌ههای ‪ (TX) ۳‬و ‪ (GND) ۵‬متصل میکنیم‪.‬چشمک زن کردن ‪ LED‬را با قطببع و‬

‫صفحه‪46‬‬
‫ میلی ثانیه یکبار وآنهم توسط لود کردن و از حافظه خارج کردن درایببور شبببیه‬۵۰۰ ‫وصل کردن جریان درهر‬
rmmod ‫ و‬insmod blink_led.ko ‫ببببرای اینکبببار بایبببد متناوب ببا ًا از دسبببتور‬.‫سبببازی میکنیبببم‬
‫ نامگببذاری‬blink_led.c ‫بنابراین کد ما باید چیزی شبیه زیر باشد که آنرا‬.‫ استفاده نماییم‬blink_led.ko
:‫میکنیم‬

#include <linux/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <linux/serial_reg.h>
#define SERIAL_PORT_BASE 0x3F8

int __init init_module()


{
int i;
u8 data;

data = inb(SERIAL_PORT_BASE + UART_LCR);


for (i = 0; i < 5; i++)
{
/* Pulling the Tx line low */
data |= UART_LCR_SBC;
outb(data, SERIAL_PORT_BASE + UART_LCR);
msleep(500);
/* Defaulting the Tx line high */
data &= ~UART_LCR_SBC;
outb(data, SERIAL_PORT_BASE + UART_LCR);
msleep(500);
}
return 0;
}

47‫صفحه‬
void __exit cleanup_module()
{
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>");
MODULE_DESCRIPTION("Blinking LED Hack");

.‫خروجی مورد نظر را مشاهده کرد‬،‫ کردن این فایل و اتصال کیت مورد نظر‬make ‫درنهایت میتوان با‬

48‫صفحه‬
‫فصل نهم ‪ :‬کنترل ورودی‪/‬خروجی در لینوکس‬

‫معرفی ‪:ioctl‬‬
‫کنترل ورودی‪/‬خروجی یا به اختصار ‪ ioctl‬یک ‪ system call‬همه کاره برای تمام درایورهاست‪.‬اگر‬
‫برای کار مشخصی که شما برای داریورتان در نظر دارید هیچ ‪ system call‬ای وجود نداشته باشد بازهم‬
‫میتوانید روی ‪ ioctl‬حساب کنید‪.‬برای مثال با ‪ ioctl‬میتوانیببد صببدا را در درایببور صببوتی کنببترل کنیببد یببا‬
‫نمایشگر را برای دستگاه گرافیک کنترل کنید و یا از دستگاه ثبت شدد‌های بخوانید و‪...‬‬
‫البته از ‪ ioctl‬نیز میتوان برای کارهایی غیر از آنچه اشاره شد ‪ ،‬از جمله دیباگ کردن یک درایور بببا پببرس و‬
‫جو از ساختار دادد‌های درایور نیز استفاده کرد‪.‬‬
‫سؤالی که ممکن است مطرح شود این است که چگونه یک تببابع ‪ ioctl‬بببدینگونه کارهببای متفبباوتی را انجببام‬
‫یدهد‪.‬جواب این پاسخ به دو پارامتر این تابع‪ ،‬یعنی »دستور« و »آرگومان« بازمیگردد‪».‬دستور« یک عدد است‬
‫م د‌‬
‫که نوع عملیا ت را مشخص میکند و »آرگومان« پارامترهای مورد نیاز »دستور« را فراهم میکنببد‪.‬بصببور ت کلببی‬
‫تابع ‪ ioctl‬را با استفاده از ساختار ‪ switch … case‬نوشتد‌هاند که با توجه به ورودی »دستور« عملیببا ت‬
‫مختلفی را اجرا میکند‪.‬‬
‫در کرنل های قدیمی ‪ ioctl‬بدینگونه تعریف شده بود‪:‬‬

‫;)‪int ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long arg‬‬

‫ولی در کرنل ‪ ۲.۶.۳۵‬به بعد بدینگونه تعریف میشود‪:‬‬

‫;)‪long ioctl(struct file *f, unsigned int cmd, unsigned long arg‬‬

‫اگر به »آرگومان«های زیادی نیاز دارید میتوانید تمام مقادیر را در قالب یک سبباختار )‪ (struct‬قراردهیببد و‬
‫نشانی این ساختار را به تابع ‪ ioctl‬معرفی کنید‪.‬چه عدد ارسالی یک مقدار صحیح باشد یا یببک اشباره گبر‪ ،‬در‬
‫فضای کرنل در نهایت به ‪ long‬تفسیر شده و پردازش میشود‪.‬‬
‫یشود و سپس یک تابع اشبباره گببری مناسببب بببا آن راه‬
‫‪ Ioctl‬معموا ًل به عنوان بخشی از درایور پیادد‌هسازی م د‌‬
‫ل برای )(‪ read‬و )(‪ write‬بکاربردیم‪.‬‬
‫یگردد‪ .‬دقیقا ًا مانند ‪ system call‬هایی که ما قب ا ً‬
‫اندازی م د‌‬

‫صفحه‪49‬‬
‫ل کار کردیم‪ ،‬یببک فیلببد اشبباره گببر بببه تببابع‬
‫در درایورهای کاراکتری و در ساختار ‪ file_opration‬که قب ا ً‬
‫یشود )البته از کرنل نسخه ‪ ۲.۶.۳۵‬به بعد(‪.‬‬
‫‪ ioctl‬نیز وجود دارد که با ‪ unlocked_ioctl‬شناخته م د‌‬
‫در فضای یوزر با استفاده از هدر فایل >‪ <sys/ioctl.h‬و به روش زیر میتوان به ایببن تببابع نوشببته شببده در‬
‫درایور دسترسی داشت‪:‬‬

‫;)‪int ioctl(int fd, int cmd, ...‬‬

‫در اینجا ‪ cmd‬نظیر آن چیزی است که در تابع ‪ ioctl‬درایور نوشته شده است و یک متغییر برای فرسببتادن‬
‫هر نوع آرگومان به درایور میباشد‪.‬نکته قایل توجه این است که »دستور« و نوع آرگومان »دسببتور« بایببد بیببن‬
‫فضای یوزر و کرنل مشابه باشد‪.‬بنابراین این تعاریف معموا ًل در هدر فایلهای هر فضا قرار میگیرید‪.‬‬

‫دسترسی به متغیر داخل درایور‬


‫برای در ک بهتره تئوری خستد‌هکننده بال‪ ،‬بهتر است یک مثالی بزنیم‪.‬بیایید مجمببوعه کببد »دیببباگ یببک‬
‫درایور« را بررسی نماییم‪.‬این درایور سه متغیر استاتیک بببه نامهببای ‪ dignity‬و ‪ status‬و ‪ ego‬دارد کببه‬
‫یشوند و عملیاتی را در درایور انجام میدهند‪.‬هببدر فایببل >‪ <query_ioctl.h‬کببه مببا تعریببف‬
‫مقداردهی م د‌‬
‫یشود مانند کد زیر‪:‬‬
‫میکنیم شامل »دستور« و نوع آرگومان »دستور« را شامل م د‌‬

‫‪#ifndef QUERY_IOCTL_H‬‬
‫‪#define QUERY_IOCTL_H‬‬

‫>‪#include <linux/ioctl.h‬‬

‫‪typedef struct‬‬

‫{‬

‫;‪int status, dignity, ego‬‬

‫;‪} query_arg_t‬‬

‫)* ‪#define QUERY_GET_VARIABLES _IOR('q', 1, query_arg_t‬‬

‫صفحه‪50‬‬
#define QUERY_CLR_VARIABLES _IO('q', 2)

#define QUERY_SET_VARIABLES _IOW('q', 3, query_arg_t *)

#endif

:‫ نوشتیم که در زیر آمده‬query_ioctl.c ‫ را در فایل‬ioctl() ‫درایور‬

#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/version.h>

#include <linux/fs.h>

#include <linux/cdev.h>

#include <linux/device.h>

#include <linux/errno.h>

#include <asm/uaccess.h>

#include "query_ioctl.h"

#define FIRST_MINOR 0

#define MINOR_CNT 1

static dev_t dev;

static struct cdev c_dev;

static struct class *cl;

51‫صفحه‬
static int status = 1, dignity = 3, ego = 5;

static int my_open(struct inode *i, struct file *f)

return 0;

static int my_close(struct inode *i, struct file *f)

return 0;

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))

static int my_ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long
arg)

#else

static long my_ioctl(struct file *f, unsigned int cmd, unsigned long arg)

#endif

query_arg_t q;

switch (cmd)

case QUERY_GET_VARIABLES:

52‫صفحه‬
q.status = status;

q.dignity = dignity;

q.ego = ego;

if (copy_to_user((query_arg_t *)arg, &q, sizeof(query_arg_t)))

return -EACCES;

break;

case QUERY_CLR_VARIABLES:

status = 0;

dignity = 0;

ego = 0;

break;

case QUERY_SET_VARIABLES:

if (copy_from_user(&q, (query_arg_t *)arg, sizeof(query_arg_t)))

return -EACCES;

status = q.status;

dignity = q.dignity;

ego = q.ego;

break;

53‫صفحه‬
default:

return -EINVAL;

return 0;

static struct file_operations query_fops =

.owner = THIS_MODULE,

.open = my_open,

.release = my_close,

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))

.ioctl = my_ioctl

#else

.unlocked_ioctl = my_ioctl

#endif

};

static int __init query_ioctl_init(void)

int ret;

struct device *dev_ret;

54‫صفحه‬
if ((ret = alloc_chrdev_region(&dev, FIRST_MINOR, MINOR_CNT, "query_ioctl")) < 0)

return ret;

cdev_init(&c_dev, &query_fops);

if ((ret = cdev_add(&c_dev, dev, MINOR_CNT)) < 0)

return ret;

if (IS_ERR(cl = class_create(THIS_MODULE, "char")))

cdev_del(&c_dev);

unregister_chrdev_region(dev, MINOR_CNT);

return PTR_ERR(cl);

if (IS_ERR(dev_ret = device_create(cl, NULL, dev, NULL, "query")))

55‫صفحه‬
class_destroy(cl);

cdev_del(&c_dev);

unregister_chrdev_region(dev, MINOR_CNT);

return PTR_ERR(dev_ret);

return 0;

static void __exit query_ioctl_exit(void)

device_destroy(cl, dev);

class_destroy(cl);

cdev_del(&c_dev);

unregister_chrdev_region(dev, MINOR_CNT);

module_init(query_ioctl_init);

module_exit(query_ioctl_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>");

56‫صفحه‬
MODULE_DESCRIPTION("Query ioctl() Char Driver");

‫ میگذاریم که در‬query_app.c ‫ تابع فراخوان را در فضای کاربر مینویسیم و نام فایل آنرا‬، ‫و در نهایت‬
:‫زیر مشاهده میکنید‬

#include <stdio.h>
#include <sys/types.h>

#include <fcntl.h>

#include <unistd.h>

#include <string.h>

#include <sys/ioctl.h>

#include "query_ioctl.h"

void get_vars(int fd)

query_arg_t q;

if (ioctl(fd, QUERY_GET_VARIABLES, &q) == -1)

perror("query_apps ioctl get");

else

57‫صفحه‬
printf("Status : %d\n", q.status);

printf("Dignity: %d\n", q.dignity);

printf("Ego : %d\n", q.ego);

void clr_vars(int fd)

if (ioctl(fd, QUERY_CLR_VARIABLES) == -1)

perror("query_apps ioctl clr");

void set_vars(int fd)

int v;

query_arg_t q;

printf("Enter Status: ");

scanf("%d", &v);

getchar();

q.status = v;

printf("Enter Dignity: ");

58‫صفحه‬
scanf("%d", &v);

getchar();

q.dignity = v;

printf("Enter Ego: ");

scanf("%d", &v);

getchar();

q.ego = v;

if (ioctl(fd, QUERY_SET_VARIABLES, &q) == -1)

perror("query_apps ioctl set");

int main(int argc, char *argv[])

char *file_name = "/dev/query";

int fd;

enum

e_get,

e_clr,

59‫صفحه‬
e_set

} option;

if (argc == 1)

option = e_get;

else if (argc == 2)

if (strcmp(argv[1], "-g") == 0)

option = e_get;

else if (strcmp(argv[1], "-c") == 0)

option = e_clr;

else if (strcmp(argv[1], "-s") == 0)

option = e_set;

else

60‫صفحه‬
{

fprintf(stderr, "Usage: %s [-g | -c | -s]\n", argv[0]);

return 1;

else

fprintf(stderr, "Usage: %s [-g | -c | -s]\n", argv[0]);

return 1;

fd = open(file_name, O_RDWR);

if (fd == -1)

perror("query_apps open");

return 2;

switch (option)

case e_get:

get_vars(fd);

break;

61‫صفحه‬
case e_clr:

clr_vars(fd);

break;

case e_set:

set_vars(fd);

break;

default:

break;

close (fd);

return 0;

‫ ( را با‬query_app.c ‫ ( را بسازید و برنامه )فایل‬query_ioctl.ko ‫ )فایل‬query_ioctl ‫درایور‬


. ‫ کنید‬make ‫ زیر‬Makefile ‫استفاده از‬

# If called directly from the command line, invoke the kernel build system.
ifeq ($(KERNELRELEASE),)

KERNEL_SOURCE := /usr/src/linux

PWD := $(shell pwd)

default: module query_app

module:

$(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) modules

62‫صفحه‬
‫‪clean:‬‬

‫‪$(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) clean‬‬

‫‪${RM} query_app‬‬

‫‪# Otherwise KERNELRELEASE is defined; we've been invoked from the‬‬

‫‪# kernel build system and can use its language.‬‬

‫‪else‬‬

‫‪obj-m := query_ioctl.o‬‬

‫‪endif‬‬

‫حال ‪ query_app.c‬و ‪ query_ioctl.c‬را با عملیا ت زیر امتحان کنید‪:‬‬

‫• درایور را با استفاده از دستور ‪ insmod query_ioctl.ko‬در کرنل لود کنید‬

‫• برنامه ‪ query_app‬را با استفاده از »دستور« و آرگومانهای خط دستور زیر اجرا نمایید‪:‬‬

‫• اجرای بدون آرگومان ‪ query_app‬برای مشاهده متغیرهای درایور‬

‫• اجرای برنامه ‪ query_app‬با آرگومان ‪ -c‬برای پا ک کردن متغییرهای درایور‬

‫• اجرای برنامه ‪ query_app‬با آرگومان ‪ -g‬برای مشاهده متغیرهای درایور‬

‫• اجرای برنامه ‪ query_app‬با آرگومان ‪ -s‬برای مقداردهی متغیرهای درایور‬

‫• حذف درایور از کرنل با استفاده از دستور ‪rmmod query_ioctl‬‬

‫صفحه‪63‬‬
‫فصل دهم ‪ :‬دیباگ کردن کرنل در لینوکس‬
‫در لینوکس و در فضای یوزر چندیدن دیباگر معروف از قبیل ‪ ddd ، gdb‬و … وجود دارند‪ .‬همینطور‬
‫در فضای کرنل نیز دیباگری بنام ‪ kgdb‬از کرنل نسخه ‪ ۲.۶.۲۶‬به بعد معرفی گردیده است‪.‬‬

‫چالش دیباگ کردن در فضای کرنل‬


‫همانگونه که برای دیباگ کردن در فضای یوزر به یک اینترفیس )رابط کاربری( احتیبباج داریببم بببرای‬
‫فضای کرنل نیز این اینترفیس به دو طریق در اختیار ما خواهد بود‪:‬‬
‫• روش نخست قراردادن دیباگر در خود کرنل میباشد که طبیعتا ًا اینترفیس نیز از طریق کنسول معمولی‬
‫در اختیار ما خواهد بود‪.‬برای انجام اینکار باید برای نسخه های کرنببل بعببد از ‪) ۲.۶.۳۵‬تببا قبببل از آن‬
‫بصبببور ت غیبببر رسبببمی نیبببز وجبببود داشبببت و میبایسبببت سبببورس دیبببباگر را از سبببایت‬
‫‪ ftp://oss.sgi.com/projects/kdb/download‬دانلود کرده و پببچ لزم را بببه کرنببل اضببافه‬
‫کرد( ‪ kdb‬را باید در سورس کرنل فعال کرد و سپس کرنل را کامپایل و راه اندازی نمببود‪.‬در هنگببام‬
‫بو ت شدن‪ ،‬خود کرنل اینترفیس دیباگ را در اختیار ما قرار میدهد‪.‬‬
‫ت راه دور یا از طریق رابط‬
‫• روش دوم راه اندازی سرو ِر دیباگ ِر کرنل میباشد که با استفاده از یک کلین ِ‬
‫شبکه یا پور ت سریال میتوان به این سرور متصل شد و دیباگ را با استفاده از ‪ gdb‬و یا ‪ kgdb‬و از‬
‫سمت کلینت انجام داد‪.‬از نسخه ‪ ۲.۶.۲۶‬اتصال از طریق پور ت سریال در کرنل گنجانببده شببده اسببت‬
‫ولی اگر شما علقمند به اتصال از طریق شبکه میباشید میبایست یک پچ را از سایت پروژه ‪ kgdb‬به‬
‫نشانی ‪ http://sourceforge.net/projects/kgdb‬دانلببود کببرده و بببه سببورس کرنببل‬
‫اضافه نمایید‪.‬‬
‫درهردوحالت شما نیاز دارید تا ‪ kgdb‬را در سورس کرنل فعال کبرده ‪ ،‬کرنببل را کامپایبل و نصبب نمببوده و‬
‫سیستم را با کرنل جدید راه اندازی نمایید‪.‬توجه داشته باشید که در هر دو حالت وبر خلف ساخت ماژول هببا ‪،‬‬
‫شما به تمام سورس کرنل نیاز دارید و تنها در اختیار داشتن هدر فایلهببا کببافی نمیباشببد‪.‬در ادامببه فقببط روش‬
‫دیباگ از طریق پور ت سریال آموزش داده میشود‪.‬‬

‫پیکربندی کرنل لینوکس با ‪kgdb‬‬


‫برای پیکربندی کرنل جهت دیباگ کردن نخست باید سورس کامل کرنل را در اختیار داشته باشببید و‬
‫یا اینکه آنرا از سایت ‪ kernel.org‬دانلود نماییببد‪.‬قبببل از هببر چیببز بایببد ‪ kgdb‬فعببال شببود‪ .‬بببرای ایببن‬

‫صفحه‪64‬‬
‫منظورکرنل باید با سوئیچ ‪ CONFIG_KGDB=y‬کانفیگ شود‪.‬بعلوه جهت دیباگ کردن از طریببق پببور ت‬
‫سریال باید سوئیچ ‪ CONFIG_KGDB_SERIAL_CONSOL=y‬نیز بکار برده شود‪.‬همچنین برای اینکه‬
‫دادد‌ههای سمبولیک توکار ‪ kgdb‬برای ما مفهوم تر باشد میتوان از سببوئیچ ‪CONFIG_DEBUG_INFO‬‬
‫قتببر نشببانگرهای فریببم در کرنببل از سببوئیچ‬
‫یشببود بببرای فهببم دقی د‌‬
‫نیببز اسببتفاده کببرد‪.‬و پیشببنهاد م د‌‬
‫نها در مواقعی که از دستورا ت زیر جهت‬
‫‪ CONFIG_FRAME_POINTER=y‬نیز استفاده شود‪.‬این آپش د‌‬
‫پیکربندی کرنل استفاده کنیم و در منوی ‪ kernel hacking‬در اختیار میباشد‪:‬‬

‫‪make mrproper‬‬ ‫‪#To clean up properly‬‬


‫‪make oldconfig‬‬ ‫‪#Configure the kernel same as the current running one‬‬
‫‪make menuconfig #Start the ncurses based menu for further configuration‬‬

‫البته جهت اجرا باید دستور سوم را حتما ًا با مجوز ‪ root‬یا با استفاده از ‪ sudo‬اجرا کرد‪.‬‬
‫نها را در شکل زیرمشاهده میکنید‪:‬‬
‫آپشن ها و منو ها و چگونگی دسترسی به آ د‌‬

‫دیباگ کرنل با ‪ gdb‬از راه دور ‪CONFIG_KGDB <---‬‬ ‫•‬


‫استفاده از ‪ kgdb‬از طریق سریال ‪CONFIG_KGDB_SERIAL_CONSOLE <---‬‬ ‫•‬
‫• کامپایل کرنل با استفاده از ‪ <--- debug info‬إ ‪CONFIG_DEBUG_INFO‬‬

‫صفحه‪65‬‬
‫• کامپایل کرنل با استفاده از فریم اشاره گرها ‪CONFIG_FRAME_POINTER <---‬‬
‫بعد از انجام تغییرا ت و ذخیره کردن آن‪ ،‬کرنل را با استفاده از دستور ‪ make‬ساخته و سببپس بببا اسببتفاده از‬
‫دستور ‪ make install‬نصب نمایید و جهت بو ت کردن سیستم با کرنل جدیببد ‪ grub‬را تغییببر دهیببد‪.‬‬
‫بسببته بببه اینکببه توزیببع شببما چیسببت بایببد فایببل تنظیمببا ت را در مسببیر هببای ‪ /etc/grub.conf‬ویببا‬
‫‪ / boot/grub/menu.lst‬بیابیببد‪ .‬وقببتی تمببام مراحببل فببوق انجببام شببد بایبد پبارامتر کرنببل ‪kgdb-‬‬
‫‪ related‬نیز همانطور که در زیر مشاهده میکنید به ‪ grub‬اضافه شود‪:‬‬

‫‪ kgdboc‬جهت اتصال ‪ gdb‬از طریق پور ت سریال میباشد که به فرمت زیر است‪:‬‬
‫>‪kgdboc = <serial device> , <baud rate‬‬
‫• >‪ : <serial device‬فایل )‪ (Port‬دستگاه سریال بروی دستگاهی کببه کرنببل بببروی آن دیببباگ‬
‫میشود‪.‬‬
‫• >‪ <baud rate‬نرخ انتقال پور ت سریال میباشد‪.‬‬

‫پارامتر ‪ kgdbwait‬به کرنل میگوید که در هنگام بو ت ‪ ،‬تا زمانی که کلینببت ‪ gdb‬بببه آن متصببل نشببده‬
‫است باید منتظر بماند‪.‬این پارامتر حتما ًا باید بعد از ‪ kgdboc‬بیاید‪.‬‬
‫الن باید یک کپی از ایمیج کرنل ساخته شده یا همان ‪ vmlinux‬برای استفاده ‪ gdb‬برداشته و سیستم را با‬
‫کرنل جدید راه اندازی نمایید‪ .‬سپس باید منتظر بمانید تا ‪ gdb‬با استفاده از پور ت سریال به آن متصل شود‪.‬‬
‫برای ‪ kgdb‬اطلعا ت‪ ،‬در فایلی شبیه ‪ /dev/ttyso‬برای اولین پور ت سریال ثبت میشود‪.‬‬

‫صفحه‪66‬‬
‫پیکربندی ‪ gdb‬بروی کلینت‬
‫کارهای زیر میبایست انجام شود‪:‬‬
‫اتصال کلینت به کامپیوتری که کرنل را اجرا میکند بببا اسببتفاده از کابببل ‪null modem‬‬ ‫•‬
‫س سریال( ‪.‬‬
‫)مانند کابل کرا ِ‬
‫کپی که از ایمیج کرنل ساخته شده گرفتد‌هاید را بروی شاخه جاری کلینت قرار دهید‪.‬‬ ‫•‬
‫در هنگامی که سیستم هدف در انتظار اتصال میباشد با استفاده از کلینببت دسببتورا ت زیببر را‬ ‫•‬
‫اجرا کنید‪:‬‬
‫‪• (gdb) file vmlinux‬‬
‫‪• (gdb) set remote interrupt-sequence Ctrl-C‬‬
‫‪• (gdb) set remotebaud 115200‬‬
‫‪• (gdb) target remote /dev/ttyS0‬‬
‫‪• (gdb) continue‬‬

‫در دستور فوق ‪ vmlinux‬همان کپی ایمیج میباشد‪.‬‬

‫صفحه‪67‬‬
‫فصل یازدهم ‪:‬درایورهای ‪USB‬درلینوکس‪-‬قسمت اول‬

‫تشخیص یک دستگاه ‪ USB‬در لینوکس‬


‫در لینوکس حتی اگر هیچ درایو ِر ‪ USB‬نیز در کرنل لود نشده باشد‪ ،‬باز لینوکس باید دستگاه ‪ USB‬ی‬
‫تافزار شناسایی کند‪.‬دلیل آن‬
‫استاندارد شما را با استفاده از قسمت ‪ USB-enabled‬در فضای کرنل و سخ د‌‬
‫این است که تمام دستگاههای استاندارد ِ ‪ USB‬با توجه به پروتکل ‪ USB‬طراحببی و سبباخته شببدد‌هاند‪ .‬فضببای‬
‫س دیوایس شبیه ‪ PCI‬در معماری ‪ (X86‬بایببد دسببتگاه‬
‫ن ‪) USB‬یک با ِ‬
‫تافزار با استفاده از کنترل ِر میزبا ِ‬
‫سخ د‌‬
‫ن ‪ USB‬میبایست فعال شود تا کار ترجمببه اطلعببا ت‬
‫را شناسایی کند‪.‬برای کار با دستگاه‪ ،‬درایو ِر کنترل ِر میزبا ِ‬
‫سطح پایین فیزیکی ‪ ،‬به اطلعاتی با مشخصا ت ‪ USB‬سطح بال را انجام دهد‪.‬مشخصا ت پروتکل ‪ USB‬درباره‬
‫دستگاههای ‪ USB‬در یک لیه مرکزی کرنل بنببام ‪ usbcore‬پیادد‌هسببازی شببده اسببت بنببابراین تشببخیص‬
‫دستگاههای ‪ USB‬حتی بدون وجود درایورشان در فضای کرنل قابل انجام است‪.‬‬
‫طها و برنامد‌ههای مختلفی )بسته به توزیع لینوکس( وجود دارد تببا نمببایی از دسببتگاه‬
‫در مرحله بعد‪ ،‬درایورها راب د‌‬
‫‪ USB‬شناسایی شده را به فضای کاربر منتقل نمایند‪.‬شکل زیر یک شمای کلی بال به پایین از زیر سیسببتمهای‬
‫‪ USB‬لینوکس را نشان میدهد‪:‬‬

‫صفحه‪68‬‬
‫برای بدست آوردن لیستی از دستگاههای ‪ USB‬شناسایی شده میتوانید دسببتور ‪ lsusb‬را بببا مجببوز ریشببه‬
‫صادر نمایید‪ .‬شکل زیر خروجی دستور ‪ lsusb‬را قبل و بعد از اتصال یببک قلببم نببوری ‪ USB‬بببه سیسببتم را‬
‫مشاهده میکنید‪:‬‬

‫شفرض لببود میگببردد‪.‬ایببن درایببور‬


‫در توزیعهایی مانند فدورا ‪ ،‬مندریوا و … درایور ‪ usbfs‬بصور ت پی د‌‬
‫کمک میکند تا اطلعا ت جزئی تر و تکنیکی تری از دسببتگاههای ‪ USB‬شناسببایی شببده را از راه ‪ /proc‬و بببا‬
‫استفاده از دستور ‪ cat /proc/bus/usb/devises‬داشته باشیم‪ .‬شکل زیر یک قسمت از همان دستگاه‬
‫قلم نوری را نشان میدهد‪ .‬این لیست معموا ًل شامل یک قسمت از هر دستگاه شناسببایی شببده توسببط لینببوکس‬
‫شماست‪:‬‬

‫صفحه‪69‬‬
‫رمزهگشایی از دستگاه ‪USB‬‬
‫برای رمزگشایی از دستگاه ‪ USB‬نخست بهتر است معماری آن را بشناسیم‪ .‬تمببام دسببتگاههای ‪USB‬‬
‫ض رایج تریببن‬
‫شفر ِ‬
‫م پی د‌‬
‫دارای یک یا چند تنظیم میباشند‪ .‬منظور از تنظیم ‪ ،‬چیزی شبیه پروفایل است که تنظی ِ‬
‫نوع را در بر دارد‪ .‬لینوکس فقط از یک تنظیم برای هر دستگاه پشتیبانی میکند )اولین پیببش فببرض(‪.‬بببرای هببر‬
‫یدهد‪.‬‬
‫تنظیم ‪ ،‬دستگاه ‪ USB‬ممکن است یک یا چند رابط داشته باشد که هر رابط یک کار مشخص را انجام م د‌‬
‫هر دستگاه ‪ USB‬میتواند رابطهای مختلفی را برای کارهای مختلف داشته باشد که بببه آن ‪MDF multi-‬‬
‫‪ ( function device‬یا »یک دستگاه با کارکرد چندگانه« گویند‪ .‬برای مثال یک چبباپگر چنببدکاره ‪USB‬‬
‫میتواند چاپ کند ‪ ،‬اسکن کند و فکس ارسال کند‪.‬پس ما در اینجا حداقل به سه رابط نیاز داریم‪ .‬بنابراین درایو ِر‬
‫طهببا‬
‫دستگاههای ‪ USB‬برعکس درایورهای دیگر بجای اینکه برای دستگاهها نوشته شببوند‪ ،‬معمببوا ًل بببرای راب د‌‬
‫طهای مختلببف‬
‫نوشته میشوند‪.‬پس ممکن است برای هر دستگا ِه ‪ USB‬چندین درایور وجود داشته باشد و یا راب د‌‬
‫یک دستگاه ‪ USB‬میتواند درایورهایی مشتر ک داشته باشند‪.‬ولی هر رابط حداکثر میتواند یک درایببور داشببته‬
‫باشد و نه بیشتر‪ .‬البته بهتر است که هر دستگاه ‪ USB‬حتی با چندین رابط نیز تنها یک درایور داشته باشد‪.‬‬

‫صفحه‪70‬‬
‫در شکل فوق )محتوی شاخه ‪ (/proc‬کلمه ‪ Driver=...‬نشان میدهد که رابببط مببورد نظببر بببه کببدام‬
‫درایور متصل شده است ‪ .‬کلمه ‪ none‬نیز نشان میدهد برای این رابط‪ ،‬درایوری تعیین نگردیده‪.‬‬
‫طها باید یک یا چند ‪ EndPoint‬وجود داشته باشد‪ .‬یک ‪ EndPoint‬شبیه لوله بببرای انتقببال‬
‫برای تمام راب د‌‬
‫ط دستگاه میباشد‪ .‬هر ‪ EndPoint‬بسته به نوع اطلعا ت میتواند به‬
‫اطلعا ت از‪/‬به )بسته به نوع عملکرد( راب ِ‬
‫مبندی شود‪.‬‬
‫چهار گوند‌هی کنترل‪ ،‬وقفه‪ ،‬حجم و همزمانی تقسی د‌‬
‫تمام دستگاههای معتب ِر‪ USB‬دارای یک ‪ EndPoint‬ای خاص مجازی صفر از نوع کنببترل میباشببند‬
‫که فقط یک ‪ EndPoint‬دوطرفه )از‪/‬به( میباشد‪ .‬شکل زیر یک نمای تصویری کامل از یک دسببتگاه ‪USB‬‬
‫استاندارد با توضیحا ت بال میباشد‪:‬‬

‫قسمتهای دستگاه یو اس بی را بیاد بیاورید )تصویرصفحه قبل( ‪ ،‬اولین حرف از هر خط ‪ ،‬انواع مشخصا ت‬
‫ل حرف ‪ D‬برای دسببتگاه‪C ،‬‬
‫قسمتهای مختلف یک دستگاه ‪) USB‬که شرح داده شد( را نمایندگی میکند‪ .‬مث ا ً‬
‫برای پیکربندی‪ I ،‬برای رابط و ‪ E‬برای ‪ Endpoint‬و‪...‬‬
‫جزئیا ت هر یک از انوع مختلف در سورس کرنل و در نشانی زیر وجود دارد‪:‬‬

‫‪/Documentation/usb/proc_usb_info.txt‬‬
‫نوشتن درایور قلم نوری ‪USB‬‬

‫صفحه‪71‬‬
‫ل اطلعا ت مببا در‬
‫برای نوشتن درایور ‪ USB‬هنوز اطلعا ت ما در مورد پروتکل ‪ USB‬کافی نیست‪ .‬مث ا ً‬
‫طها‪ ،‬خطوط انتقال اطلعا ت و چهار نوع از ‪ Endpoint‬ها که بحث شد‪ .‬همچنیببن‬
‫مورد تنظیما ت دستگاه‪ ،‬راب د‌‬
‫علیم اختصاری که در شکل دیدیم از جمله ‪ S,B,T‬زیاد مفهوم نیست‪.‬‬
‫اما نگران نباشید‪.‬جزئیا ت به تدریج شرح داده خواهد شد‪.‬ولی نخست بیایید اولین رابط مرتبط با درایور دسببتگاه‬
‫قلم نوری ‪ USB‬بنام ‪ pen_register.ko‬را بنویسیم‪.‬‬
‫مشابه درایورهایی که برای دستگاههای دیگر نوشتیم‪ ،‬برای درایورهای دستگاههای ‪ USB‬نیببز بببه تببابع‬
‫ل نوشببتیم‬
‫بگر نیاز داریم‪ .‬برای سرعت بیشتر میتوانیم از الگوی درایورهای دیگببری کببه قب ا ً‬
‫سازنده و تابع تخری د‌‬
‫نها متفاو ت خواهد بود‪ .‬تفاو ت اینجاست که در درایورهای دیگر کبار ثبببت و عببزل‬
‫استفاده کنیم ولی محتوای آ د‌‬
‫ل لیه مرتبط استفاده شببود‪.‬‬
‫درایور به ‪ VFS‬مرتبط میشود ولی در درایورهای دستگاههای ‪ USB‬باید از پروتک ِ‬
‫دراین حالت هستد‌هی ‪ USB‬بجای اینکه یک شبه فایل در فضای یوزر ارائه کند‪ ،‬میبایست به دستگاه واقعی در‬
‫تافزار متصل گردد‪.‬‬
‫فضای سخ د‌‬
‫‪ API‬های هسته ‪ USB‬برای انجام ثبت و عزل درایور به ترتیببب زیببر میباشببد)ایببن ‪ API‬هببا در هببدر فایببل‬
‫>‪ <linux/usb.h‬تعریف شده اند(‪:‬‬

‫;)‪int usb_register(struct usb_driver *driver‬‬


‫;)* ‪void usb_deregister(struct usb_driver‬‬

‫ساختار ‪ usb_driver‬شامل فیلدهای نام درایورها و جدول ‪ ID‬برای شناسایی خودکار چندین دسببتگاه‬
‫‪ USB‬و همچنین دو تابع جهت هندل کردن اتصال و انفصال دستگاه ‪ USB‬بصور ت بلدرنگ در هسته ‪USB‬‬
‫میباشد‪ .‬بیایید به فایل ‪ pen_register.c‬نگاهی بیاندازیم‪:‬‬

‫>‪#include <linux/module.h‬‬
‫>‪#include <linux/kernel.h‬‬
‫>‪#include <linux/usb.h‬‬

‫)‪static int pen_probe(struct usb_interface *interface, const struct usb_device_id *id‬‬


‫{‬
‫‪printk(KERN_INFO "Pen drive (%04X:%04X) plugged\n", id->idVendor, id-‬‬
‫;)‪>idProduct‬‬
‫;‪return 0‬‬
‫}‬

‫صفحه‪72‬‬
static void pen_disconnect(struct usb_interface *interface)
{
printk(KERN_INFO "Pen drive removed\n");
}

static struct usb_device_id pen_table[] =


{
{ USB_DEVICE(0x058F, 0x6387) },
{} /* Terminatingentry */
};
MODULE_DEVICE_TABLE (usb, pen_table);

static struct usb_driver pen_driver =


{
.name = "pen_driver",
.id_table = pen_table,
.probe = pen_probe,
.disconnect = pen_disconnect,
};

static int __init pen_init(void)


{
return usb_register(&pen_driver);
}

static void __exit pen_exit(void)


{
usb_deregister(&pen_driver);
}

module_init(pen_init);
module_exit(pen_exit);

73‫صفحه‬
‫;)"‪MODULE_LICENSE("GPL‬‬
‫;)">‪MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com‬‬
‫;)"‪MODULE_DESCRIPTION("USB Pen Registration Driver‬‬

‫سپس موارد معمول برای ساخت یک درایور را تکرار میکنیم‪:‬‬


‫• ساخت درایور )‪ (file pen_register.ko‬بوسیله دستور ‪make‬‬
‫• لود کردن درایور با استفاده از دستور ‪insmod‬‬
‫• نمایش لیست درایور ها با استفاده از دستور ‪lsmod‬‬
‫• حذف درایور لود شده از حافظه با استفاده از دستور ‪rmmod‬‬

‫یبینم خروجی برنامه آن چیزی نیست کببه انتظببارش را داشببتیم‪ dmesg .‬و زیببر‬
‫اما با کمال تعجب م د‌‬
‫شاخه ‪ / proc‬را چک کنید تا جزئیا ت بیشتری دستگیرتان شود‪ .‬همانطور که در قبل اشاره شد درایورهای یببو‬
‫اس بی متفاو ت از درایورهای کاراکتری هستند‪ .‬دو شکل بالتر نشان میدهد که دستگاه قلببم نببوری یببک رابببط‬
‫ل به درایور ذخیره ساز یو اس بی )‪ (usb-storage‬متصل شده است‪.‬‬
‫دارد )‪ (number 0‬که قب ا ً‬
‫حال به منظور ارتباط درایورمان با آن رابط ‪ ،‬ما نیاز داریم تا ذخیره ساز یو اس بی کببه لببود شببده اسببت را بببا‬
‫استفاده از دستور ‪ rmmod usb-storage‬از حافظه کرنل حذف کنیم و بجای آن درایببور قلببم نببوری‬
‫‪ USB‬را بارگذاری کنیم‪ .‬به محض اینکه اینکار انجام شود نتیجه مورد انتظار ما ظاهر خواهد شببد‪ .‬شببکل زیببر‬
‫یک نگاه اجمالی از لگ مورد نظر میباشد‪ .‬با اتصال و انفصال بلدرنگ قلم نوری از کامپیوتر میتوانید فراخببوانی‬
‫های متناظر را نیز ببینید‪.‬‬

‫صفحه‪74‬‬
‫صفحه‪75‬‬
‫لدوازدهم‪:‬درایورهای ‪USB‬درلینوکس‪-‬قسمت‬
‫فص د‌‬
‫دوم‬
‫درفصل قبل دیدیم که کد یکتای کارخانه سازنده قلم نوری ‪ 0x058f‬و کد یکتای محصول ‪ 0x6387‬بود‪ .‬قبل‬
‫از اینکه با هم کد فصل قبل را کامل کنیم ابتدا لزم است مفاهیم بیشتری را از پروتکل ‪ USB‬بدانیم‪.‬‬

‫‪ Endpoint‬ها در ‪ USB‬و انواع آن‬


‫بسته به نوع و خصوصیا ت اطلعاتی که میبایست منتقل شود‪ ،‬یک دستگاه ‪ USB‬میتواند یک یا چنببد‬
‫‪ Endpoint‬داشته باشد و هر ‪ Endpoint‬میتواند یکی از انواع زیر باشد‪:‬‬

‫• کنترل ‪ :‬برای ارسال اطلعا ت کنترلی – بعنوان مثال ریست کردن دستگاه ‪ ،‬گرفتن اطلعببا ت وضببعیت‬
‫شفرض ‪ Endpoint‬کنترل بببه صببفر اشبباره‬
‫دستگاه و غیره‪ .‬در تمام دستگاههای ‪ USB‬بصور ت پی د‌‬
‫میکند‪.‬‬
‫• وقفه ‪ :‬برای ارسال اطلعا ت کوتاه و سریع ‪ ،‬معموا ًل تا ‪ 8‬بایت از وقفه اسببتفاده میشببود ‪ .‬بعنببوان مثببال‬
‫انتقال اطلعا ت برای پور ت سریال یا دستگاههای رابط شخصی مانند کیبورد و ماوس و غیره‪.‬‬
‫• انبوه ‪ :‬برای انتقال اطلعا ت حجم انبوه و در مقایسه با وقفه کم سرعت تر استفاده میگردد ‪ .‬بعنوان یک‬
‫مثال میتوان به انتقال اطلعا ت به یک فلش مموری اشاره کرد ‪.‬‬
‫• متقارن ‪:‬برای حجم زیاد اطلعا ت با تضمین پهنای باند ثابت از این نوع استفاده میشببود‪.‬ولببی جببامعیت‬
‫اطلعا ت ممکن است در این روش تضمین نشود‪ .‬بعنوان مثال میتوان به انتقبال اطلعبا ت حسباس ببه‬
‫زمان مانند صدا و تصویر اشاره کرد‪.‬‬

‫به جز ‪ Enpoint‬کنترل‪ ،‬بقیه ‪ Endpoint‬ها بصور ت ورودی و خروجی میتوانند عمببل کننببد‪ .‬بببدین‬
‫معنی که اطلعا ت از دستگاه ‪ USB‬میتواند به کامپیوتر منتقل گردد و برعکس‪ .‬هر ‪ Endpoint‬با اسببتفاده‬
‫از ‪ ۸‬بیت شناسایی میگردد که پر ارزشترین بیت )‪ (MSB‬نشانگر مسیر اطلعا ت )صفر برای خروجببی و یببک‬
‫برای ورودی( میباشد‪ Endpoint .‬د‌ کنترل دوطرفه میباشد بدین معنی که این بیت نادیده گرفته میشود‪.‬‬
‫شکل زیر مشخصا ت دستگاه ‪ USB‬متصل شده به یک کامپیوتر را نشان میدهد‪:‬‬

‫صفحه‪76‬‬
‫خطوطی که با ‪ E‬شروع شدد‌هاند ‪ Endpoint‬هستند که در شکل فوق یببک ‪ Endpoint‬از نببوع‬
‫»وقفه« و دو ‪ Endpoint‬از نوع »انبوه« و مربوط به کنترلر مرکزی ‪ UHCI‬دستگاه قلم نوری میباشند‪.‬‬
‫اعدا ِد ‪ Endpoint‬به هگزا دسیمال بوده که ببه ترتیبب ‪ 0x81‬و ‪ 0x01‬و ‪ 0x82‬میباشببد‪ .‬بیبت پبرارزش‬
‫‪ Endpoint‬اول و سببوم بببا )‪ ( I‬نشببان داده شببده کببه بیببانگر ورودی میباشببد‪ .‬همچنیببن بیببت پببرارزش‬
‫‪ Endpoint‬دوم با )‪ (0‬نشان داده شده که بیانگر خروجی میباشد‪ MXPS .‬نیز نشانگر ماکزیمم سایز پکت‬
‫د‌‬
‫یشود را تعیین میکند‪ .‬همببانطور کببه در شببکل‬
‫میباشد یعنی بیشترین سایز اطلعاتی که دفعتا ًا میتواند منتقل م د‌‬
‫میبینیم ‪» Endpoint‬وقفه« ‪ ۲‬بیت بوده و ‪» Endpoint‬انبوه« ‪ ۶۴‬بیت میباشد‪ Ivl .‬نیببز نشببانگر فاصببله‬
‫زمانی بین انتقال دو پکت اطلعا ت متوالی برای انتقال مناسب میباشدکه برای وقفد‌هها بسیار مهم و قابل تببوجه‬
‫است‪.‬‬

‫آنالیز قسمتهای مختلف یک دستگاه ‪USB‬‬

‫صفحه‪77‬‬
‫ل در مورد خطوطی که با ‪ E:‬مشخص شده بودند بحث کردیم‪ ،‬حال زمان مناسبببی‬
‫از آنجایی که ما قب ا ً‬
‫است تا در مورد بقیه فیلدهای مرتبط نیز صحبت کنیببم‪ .‬بصببور ت خلصببه ایببن خطببوط یببک دیببد کباملی از‬
‫خصوصیا ت دستگاه یو اس بی را به ما میدهند‪.‬‬
‫با مراجعه به شکل قبل‪ ،‬اولین حرف از اولین خط هر بخش از دستگاه ‪ T‬هست و نشببانگر مببوقعیت دسببتگاه در‬
‫درخت یو اس بی میباشدکه بوسیله سه جزء شماره باس ‪ ، USB‬سطح درخت ‪ USB‬و پور ت ‪ USB‬شناخته‬
‫میشود‪ .‬حرف ‪ D‬نشانگر توضیحا ت دستگاه است و شامل حداقل نسخه دستگاه ‪ ،‬دسته بندی‪/‬کلس دسبتگاه و‬
‫تعداد تنظیما ت در دسترس برای این دستگاه میباشد‪.‬‬
‫چندین حرف ‪ C‬در خطوطی از شکل فوق وجود دارد اگرچه معموا ًل یکی بیشتر نیست‪ .‬خطوطی که با حببرف ‪C‬‬
‫شروع میشوند شرح و شاخصی اسببت بببرای تنظیمببا ت خصوصببیا ت ایببن دسببتگاه‪ ،‬حببداکثر قببدر ت )واقعببی و‬
‫فعلی(دستگاه و تعداد ‪ Interface‬ها باید در این تنظیما ت نوشته شود‪.‬‬
‫باتوجه به توضیحا ت بال ‪ ،‬حداقل یک خط ‪ I‬نیز باید وجود داشته باشد و در مببواقعی کببه ‪ Interface‬هببای‬
‫ترتیبی وجود داشته باشد میتواند تعداد بیشتری نیز باشد‪ .‬مانند وقتی که شماره ‪ Interface‬ها یکسان است‬
‫ولی خصوصیاتی متفاو ت دارند بعنوان یک مثال میتوان به وب کم ها اشاره کرد‪.‬‬
‫حرف ‪ I‬مشخص کننده شرح ‪ Interface‬به همراه شاخص‪ ،‬شماره ترتیب‪ ،‬قابلیت کلس‪/‬دسببته ایببن رابببط‪،‬‬
‫درایوری که به این رابط متصل شده و شماره ‪ Endpoint‬این ‪ Interface‬میباشد‪.‬‬
‫کلس ‪ Interface‬ممکن است مشابه کلس دستگاه باشد و یا نباشببد‪.‬و بسببته بببه شببماره ‪، Endpoint‬‬
‫ممکن است چندیدن خط ‪ E‬وجود داشته باشدکه جزئیا ت آن بعدا ًا مورد بحث قرار خواهد گرفت‪.‬‬
‫نشانه * بعد از ‪ C‬و ‪ I‬به ترتیب نشانگر تنظیمببا ت و ‪ Interface‬جبباری و فعببال اسببت ‪ .‬خببط ‪ P‬شناسبد‌هی‬
‫فروشنده ‪ ،‬شناسد‌هی محصول و نسخد‌هی دستگاه را مشخص میکند ‪ .‬خطوط ‪ S‬نیز شرح مشخصببا ت فروشببنده و‬
‫اطلعا ت توضیحی در مورد دستگاه را نشان میدهد‪.‬‬
‫برای کشف اینکه چه دستگاههایی شناسایی شدد‌هاند و همچنین مشبباهده اطلعببا ت دسببتگاههای ‪، USB‬خببوب‬
‫است دستور ‪ cat /proc/bus/usb/devices‬را اجرا کنید‪ .‬شاید اکثر این اطلعببا ت بببرای نوشببتن‬
‫درایور دستگاه مورد نیاز باشد اما آیا راهی برای نوشتن برنامه با کد ‪ C‬و با توجه به این اطلعا ت وجود دارد؟‬
‫جواب مثبت است و قصد داریم در ادامه همین کار را انجام دهیم‪ .‬آیا به خاطر مببی آوریببد بببه محببض اینکببه‬
‫دستگاه ‪ USB‬به کامپیوتر وصل شد درایور کنترلر هاست ‪ USB‬این اطلعا ت را در لیه هسته ‪ USB‬تغییببر‬
‫داد؟ و اطلعا ت را در مجموعد‌های از ‪ structure‬ها همانطور که مشخصا ت ‪ USB‬تعریف کرده قرار داد؟‬
‫یبینید که برای وضوح بیشتر بصور ت معکببوس آمببده‬
‫در زیر ساختار تعریف شده در >‪ <linux/usb.h‬را م د‌‬
‫است‪:‬‬

‫صفحه‪78‬‬
struct usb_device
{

struct usb_device_descriptor descriptor;
struct usb_host_config *config, *actconfig;

};
struct usb_host_config
{
struct usb_config_descriptor desc;

struct usb_interface *interface[USB_MAXINTERFACES];

};
struct usb_interface
{
struct usb_host_interface *altsetting /* array */, *cur_altsetting;

};
struct usb_host_interface
{
struct usb_interface_descriptor desc;
struct usb_host_endpoint *endpoint /* array */;

};
struct usb_host_endpoint
{
struct usb_endpoint_descriptor desc;

};

79‫صفحه‬
‫با دسترسی به هندل ساختار ‪ usb_device‬یک دسببتگاه مشببخص‪ ،‬میتببوان بببه تمببام اطلعببا ت و‬
‫مشخصا ت ‪ USB‬مربوط به آن دستگاه‪ ،‬همانگونه که در ‪ /proc‬موجود است دست یافت‪.‬اما چگونه میتببوان‬
‫هندل ساختار ‪ usb_device‬را پیدا کرد؟‬
‫یباشبد بلکبه هنببدل ‪) Interface‬اشباره گبر بببه سباختار‬
‫هندل دستگاه مستقیما ًا در درایور در دسبترس نم د‌‬
‫‪ (usb_interface‬در دسترس میباشد‪ .‬بیاد بیاورید که درایورهای ‪ usb‬برای ‪Interface‬د‌ هببا نوشببته‬
‫یشدند و نه برای کل دسببتگاه‪ .‬اگببر نگبباهی دوبباره بببه تواببع ‪ probe‬و ‪ disconnect‬برنبامد‌هی قبببل‬
‫م د‌‬
‫یبینید که هستد‌هی مرکزی ‪ USB‬به ازای هر رابط دستگاه فراخوانی میشببد‪..‬هنببدل ‪Interface‬‬
‫بیاندازید م د‌‬
‫نیز در اولین پارامتر موجود میباشد‪ .‬به قسمتی از آن برنامه توجه کنید‪:‬‬

‫;)‪int (*probe)(struct usb_interface *interface, const struct usb_device_id *id‬‬


‫;)‪void (*disconnect)(struct usb_interface *interface‬‬

‫با اشاره گر ‪ ، Interface‬تمام اطلعا ت مربوط به آن ‪ Interface‬قابل دسترس میباشد و بببرای گرفتببن‬
‫هندل دستگاه ماکروی زیر مورد استفاده قرار میگیرد‪:‬‬

‫;)‪struct usb_device device = interface_to_usbdev(interface‬‬

‫حال با آموخته هایمان ‪ ،‬کد زیر را در فایل ‪ pen_info.c‬مینویسیم‪:‬‬

‫>‪#include <linux/module.h‬‬
‫>‪#include <linux/kernel.h‬‬
‫>‪#include <linux/usb.h‬‬

‫;‪static struct usb_device *device‬‬

‫‪static int pen_probe(struct usb_interface *interface, const struct usb_device_id‬‬


‫)‪*id‬‬
‫{‬
‫;‪struct usb_host_interface *iface_desc‬‬
‫;‪struct usb_endpoint_descriptor *endpoint‬‬
‫;‪int i‬‬

‫صفحه‪80‬‬
iface_desc = interface->cur_altsetting;
printk(KERN_INFO "Pen i/f %d now probed: (%04X:%04X)\n",
iface_desc->desc.bInterfaceNumber, id->idVendor, id->idProduct);
printk(KERN_INFO "ID->bNumEndpoints: %02X\n",
iface_desc->desc.bNumEndpoints);
printk(KERN_INFO "ID->bInterfaceClass: %02X\n",
iface_desc->desc.bInterfaceClass);

for (i = 0; i < iface_desc->desc.bNumEndpoints; i++)


{
endpoint = &iface_desc->endpoint[i].desc;

printk(KERN_INFO "ED[%d]->bEndpointAddress: 0x%02X\n",


i, endpoint->bEndpointAddress);
printk(KERN_INFO "ED[%d]->bmAttributes: 0x%02X\n",
i, endpoint->bmAttributes);
printk(KERN_INFO "ED[%d]->wMaxPacketSize: 0x%04X (%d)\n",
i, endpoint->wMaxPacketSize, endpoint->wMaxPacketSize);
}

device = interface_to_usbdev(interface);
return 0;
}

static void pen_disconnect(struct usb_interface *interface)


{
printk(KERN_INFO "Pen i/f %d now disconnected\n",
interface->cur_altsetting->desc.bInterfaceNumber);
}

static struct usb_device_id pen_table[] =


{
{ USB_DEVICE(0x058F, 0x6387) },
{} /* Terminatingentry */

81‫صفحه‬
};
MODULE_DEVICE_TABLE (usb, pen_table);

static struct usb_driver pen_driver =


{
.name = "pen_driver",
.probe = pen_probe,
.disconnect = pen_disconnect,
.id_table = pen_table,
};

static int __init pen_init(void)


{
return usb_register(&pen_driver);
}

static void __exit pen_exit(void)


{
usb_deregister(&pen_driver);
}

module_init(pen_init);
module_exit(pen_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <[email protected]>");
MODULE_DESCRIPTION("USB Pen Info Driver");

:‫سپس مراحل تکراری را برای ساخت داریور دوباره انجام میدهیم‬


make ‫ ( با استفاده از دستور‬pen_info.ko ‫• ساخت درایور )فایل‬
insmod pen_info.ko ‫• لود کردن درایور با استفاده از دستور‬
(‫ در حافظه قرار ندارد‬usb-storage ‫• اتصال قلم نوری )بعد از آنکه مطمئن شدید که‬
‫• انفصال قلم نوری‬

82‫صفحه‬
‫• مشاهده خروجی ‪dmesg‬‬
‫• حذف درایور از حافظه با استفاده از دستور ‪rmmod pen_info‬‬

‫شکل زیر مراحل فوق را در کامپیوتر من نشان میدهد‪ .‬برای اطمینان بخاطر بیاورید که )با اسببتفاده از خروجببی‬
‫دستور ‪ (cat /proc/bus/usb/devices‬درایور ‪ usb-storage‬معمولی هیچ اتصالی با رابط قلم‬
‫نوری نداشته باشد‪.‬‬

‫دو مکببانیزم بببرای اینکببه یببک دسببتگاه بببرای هسببتد‌هی مرکببزی ‪ USB‬و بببا اسببتفاده از سبباختار جببدول‬
‫‪ usb_device_id‬شناسایی گردد برای یک درایور وجود دارد‪ .‬نخست اینکه زوج شناسه فروشنده و شناسه‬
‫محصول با استفاده از ماکروی )(‪ USB_DEVICE‬تعیین گردد که در کد فوق نیز استفاده شد‪ .‬و دوم اینکه با‬
‫استفاده از ماکروی )(‪ usb_device_info‬کلس‪/‬دستد‌هی دستگاه مشخص گببردد‪ .‬در حقیقببت ماکروهببای‬
‫بهای مختلف وجود دارد‪.‬علوه بر این چندین عدد از این ماکروهببا‬
‫بیشتری در>‪ <linux/usb.h‬برای ترکی د‌‬
‫میتوانند برای جدول ‪) usb_device_id‬که با ورودی ‪ null‬آخرین آن مشخص میشود( تعیین شوند‪ .‬کببه‬
‫نها با ضوابطی تطابق پیدا میکنند و ما قادریم برای چندین دستگاه یک درایور بنویسیم‪.‬‬
‫هر کدام آ د‌‬

‫صفحه‪83‬‬
‫لسیزدهم‪:‬انتقال اطلعا ت از‪/‬به دستگاه ‪USB‬‬
‫فص د‌‬
‫در این فصل به این سؤال پاسخ خواهیم داد که چگونه درایور‪ ،‬انتخاب میکندکه چببه ‪ Interface‬ای را‬
‫ثبت کند و از چه ‪ Interface‬ای استفاده نماید؟‬
‫پاسخ به این سؤال به مقدار بازگشتی از تابع ‪ probe‬بازمیگردد‪ .‬نکته اینجاست کببه هسببتد‌هی مرکببزی ‪USB‬‬
‫ل ثبببت شببدد‌هاند را‬
‫نهببایی کببه قب ا ً‬
‫باید ‪ probe‬های همه ‪ Interface‬های تمام دستگاه متصل را به جببز آ د‌‬
‫درخواست کند‪ .‬بنابراین مقادیر بازگشتی ‪ probe‬ها برای اولین بببار و بببرای تمببام ‪ Interface‬هببا گرفتببه‬
‫میشود‪ .‬اگر مقدار بازگشتی یک ‪ probe‬صفر بود بدین معنی است کببه درایببور بببرای آن ‪ Interface‬قب ا ً‬
‫ل‬
‫‪ Interface‬انجببام‬
‫د‌‬ ‫ثبت گردیده ولی اگر مقدار خطایی بازگردانده شود بدین معنی است که اینکار برای ایببن‬
‫نشده است‪.‬‬
‫قبل از آنکه در مببورد انتقببال نامحببدود اطلعببا ت بببه‪/‬از دسببتگاه ‪ USB‬صببحبت کنیببم ‪ ،‬در مببورد‬
‫‪ MODULE_DEVICE_TABLE‬بحببث میکنیببم‪ MODULE_DEVICE_TABLE .‬عمببدتا بببرای‬
‫‪ depmod‬در فضای یوزر میباشد‪ Module .‬یک اصطلح دیگر برای درایور است کببه میتوانببد بصببور ت‬
‫داینامیک لود و یا از حافظه حذف گردد‪ .‬ماکروی ‪ MODULE_DEVICE_TABLE‬دو متغیببر در بخببش‬
‫یشود و در فایلهببایی عمببومی در زیببر شبباخه‬
‫فقط خواندنی ماژولها میسازد که بوسیله ‪ depmod‬استخراج م د‌‬
‫>‪ /lib/modules/<kernel_version‬پیادد‌هسازی شببده اسببت‪ .‬فایلهببای ‪ modules.usbmap‬و‬
‫نها ما قادریم درایورها را‬
‫‪ modules.pcimap‬به ترتیب برای دستگاههای ‪ USB‬و ‪ PCI‬هستند که با آ د‌‬
‫بصور ت اتوماتیک لودکنیم‪ .‬همانگونه که در لود اتوماتیک درایور ‪ usb-storage‬دیدیم‪.‬‬

‫انتقال اطلعات در یواس بی‪:‬‬

‫بیایید بر پایه کدی که در فصل قبل برای قلم نوری با کد فروشندد‌هی ‪ 0x058f‬وکد محصببول ‪0x6387‬‬
‫نوشتیم کار انتقال اطلعا ت را توسعه دهیم‪.‬‬
‫تافزار است و معموا ًل به شکل لیه افقی در فضای کرنل است و از اینببرو بببرای اینکببه‬
‫‪ USB‬یک پروتکل سخ د‌‬
‫یک ‪ Interface‬ای را در فضای یوزر ایجاد کند به یکی از لیه های عمودی متصل میگردد‪.‬‬
‫ل در مورد آن بحث شد یک گزینه عالی برای برقببراری ارتببباط بببا لیببه افقببی‬
‫درایور عمودی کاراکتری که قب ا ً‬
‫‪ USB‬است که بتوانیم تمام جریان انتقال اطلعا ت را در ک کنیم‪.‬‬

‫صفحه‪84‬‬
‫اما ما نیازی نداریم تا شماره اصلی رزرو نشده وآزاد کاراکتری را بگیریم بلکه میتوانیم از شماره اصلی کاراکتری‬
‫‪ 180‬که برای فایلهای دستگاه کاراکتری رزرو شده است اسببتفاده کنیببم‪.‬بعلوه بببرای دسببتیابی کامببل درایببور‬
‫کاراکتری با لیه افقی ‪ USB‬وآنهم در یک مرحله‪ API ،‬های زیر تعریف شده اند‪:‬‬

‫‪int usb_register_dev(struct usb_interface *intf, struct usb_class_driver‬‬


‫;)‪*class_driver‬‬
‫‪void usb_deregister_dev(struct usb_interface *intf, struct usb_class_driver‬‬
‫;)‪*class_driver‬‬

‫معموا ًل ما انتظار داریم این توابع را به ترتیب در سازنده و تخریببب گببر ماژولمببان فراخببوانی کنیببم ولببی بببرای‬
‫دستیابی به رفتار ‪ hot-plug-and-play‬برای فایلهای دستگاه )کاراکتری( مرتبط با دستگاههای ‪، USB‬‬
‫نها باید به ترتیب در توابع ‪ probe‬و ‪ disconnect‬فراخوانی شوند‪.‬‬
‫آ د‌‬
‫اولین پارامتر توابع فوق اشاره گر به ‪ Interface‬است کببه در اولیببن پببارامتر ‪ probe‬و ‪disconnect‬‬
‫دریبببافت شبببده اسبببت‪ .‬پبببارامتر دوم ‪ ،‬سببباختار ‪ usb_class_driver‬میباشبببد ‪ ،‬قببببل از آنکبببه‬
‫‪ usb_register_dev‬فراخوانی شود نیاز است تا مجموعد‌های از عملیا ت فایل دستگاه و نببام فایببل دسببتگاه‬
‫پیشنهادی پر شود‪ .‬برای استفاده های واقعی ‪ ،‬به توابع ‪ pen_probe‬و ‪ pen_disconnect‬در کببدی‬
‫که بعدا ًا خواهیم نوشت )‪( pen_driver.c‬رجوع کنید‪.‬‬
‫بعلوه میتوانیم از عملیا ت روی فایل )خواندن ‪ ،‬نوشببتن و‪ (...‬اسببتفاده کنیببم و آن دقیقبا ًا چیببزی اسببت کببه مببا‬
‫نیازداریم تا دادد‌هها را به‪/‬از ‪ USB‬انتقال دهیم‪ pen_write .‬و ‪ pen_read‬که در زیر نوشته شببده اسببت‬
‫برای این منظور است و همچنین تابع )(‪) usb_bulk_msg‬که در >‪ <linux/usb.h‬آمببده اسببت( نیببز‬
‫برای انتقال حجم انبوه اطلعا ت به دستگاه قلم نوری بببا ‪ Endpoint‬هگببز ‪ 0x01‬و ‪ 0x82‬اسببت کببه بببه‬
‫ترتیب فراخوانی میشود‪ .‬برای لیست ‪ Endpoint‬ها به خطوطی که با ‪ E‬در شکل بعد شروع میشود تببوجه‬
‫کنید‪:‬‬

‫صفحه‪85‬‬
‫جهت یافتن لیست کاملی از ‪ API‬های هسته ‪ USB‬برای توابع انتقال اطلعببا ت بببا ‪ Endpoint‬مشببخص و‬
‫متفاو ت شبببیه توابببع )(‪ usb_control_msg‬و )(‪ usb_interrupt_msg‬و غیببره بببه هببدر فایببل‬
‫>‪ <linux/usb.h‬در سورس کرنل رجوع کنید‪.‬‬
‫)(‪ usb_rcvbulkpipe‬و )(‪ usb_sndbulkpipe‬و سایر ماکروهای دیگر که نیز در هدر فایل فببوق‬
‫تعریف شدد‌هاند‪ .‬بیت شاخص‪ Endpoint ،‬برای اتصال به ‪ API‬های هسته ‪ USB‬را محاسبه میکند‪.‬‬
‫یرود مجمببوعد‌های از‬
‫نکته اینکه درایور قلم نببوری بببه کلس ‪ mass storage‬تعلببق دارد کببه انتظببار م د‌‬
‫دستورا ت شبیه ‪ SCSI‬برای انتقال انبوه اطلعا ت استفاده شود‪ .‬بنابراین یک خواندن‪/‬نوشتن ناقصی که در کببد‬
‫یرود دادد‌های را جابجا نکند‪ .‬مگر اینکه دستورا ت با فرمت‬
‫زیر آمده است ممکن است واقعا ًا آنطوری که انتظار م د‌‬
‫مناسب باشد‪ .‬اما کد زیر یک خلصد‌های از جریان کلی درایور ‪ Usb‬است‪ .‬بببرای دسببتیابی بببه حببس واقعببی و‬
‫زیبای کار با انتقال اطلعا ت باید با دستگاههای ‪ USB‬مختلف شبیه آنچیزی که در نشانی زیر میباشد دست و‬
‫پنجه نرم کنید‪:‬‬

‫‪http://lddk.esrijan.com/‬‬
‫و اما کد زیر ‪:‬‬

‫صفحه‪86‬‬
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>

#define MIN(a,b) (((a) <= (b)) ? (a) : (b))


#define BULK_EP_OUT 0x01
#define BULK_EP_IN 0x82
#define MAX_PKT_SIZE 512

static struct usb_device *device;


static struct usb_class_driver class;
static unsigned char bulk_buf[MAX_PKT_SIZE];

static int pen_open(struct inode *i, struct file *f)


{
return 0;
}
static int pen_close(struct inode *i, struct file *f)
{
return 0;
}
static ssize_t pen_read(struct file *f, char __user *buf, size_t cnt, loff_t *off)
{
int retval;
int read_cnt;

/* Read the data from the bulk endpoint */


retval = usb_bulk_msg(device, usb_rcvbulkpipe(device, BULK_EP_IN),
bulk_buf, MAX_PKT_SIZE, &read_cnt, 5000);
if (retval)
{
printk(KERN_ERR "Bulk message returned %d\n", retval);
return retval;
}

87‫صفحه‬
if (copy_to_user(buf, bulk_buf, MIN(cnt, read_cnt)))
{
return -EFAULT;
}

return MIN(cnt, read_cnt);


}
static ssize_t pen_write(struct file *f, const char __user *buf, size_t cnt, loff_t *off)
{
int retval;
int wrote_cnt = MIN(cnt, MAX_PKT_SIZE);

if (copy_from_user(bulk_buf, buf, MIN(cnt, MAX_PKT_SIZE)))


{
return -EFAULT;
}

/* Write the data into the bulk endpoint */


retval = usb_bulk_msg(device, usb_sndbulkpipe(device, BULK_EP_OUT),
bulk_buf, MIN(cnt, MAX_PKT_SIZE), &wrote_cnt, 5000);
if (retval)
{
printk(KERN_ERR "Bulk message returned %d\n", retval);
return retval;
}

return wrote_cnt;
}

static struct file_operations fops =


{
.open = pen_open,
.release = pen_close,
.read = pen_read,

88‫صفحه‬
.write = pen_write,
};

static int pen_probe(struct usb_interface *interface, const struct usb_device_id


*id)
{
int retval;

device = interface_to_usbdev(interface);

class.name = "usb/pen%d";
class.fops = &fops;
if ((retval = usb_register_dev(interface, &class)) < 0)
{
/* Something prevented us from registering this driver */
err("Not able to get a minor for this device.");
}
else
{
printk(KERN_INFO "Minor obtained: %d\n", interface->minor);
}

return retval;
}

static void pen_disconnect(struct usb_interface *interface)


{
usb_deregister_dev(interface, &class);
}

/* Tableof devices that work with this driver */


static struct usb_device_id pen_table[] =
{
{ USB_DEVICE(0x058F, 0x6387) },

89‫صفحه‬
{} /* Terminatingentry */
};
MODULE_DEVICE_TABLE (usb, pen_table);

static struct usb_driver pen_driver =


{
.name = "pen_driver",
.probe = pen_probe,
.disconnect = pen_disconnect,
.id_table = pen_table,
};

static int __init pen_init(void)


{
int result;

/* Register this driver with the USB subsystem */


if ((result = usb_register(&pen_driver)))
{
err("usb_register failed. Error number %d", result);
}
return result;
}

static void __exit pen_exit(void)


{
/* Deregister this driver with the USB subsystem */
usb_deregister(&pen_driver);
}

module_init(pen_init);
module_exit(pen_exit);

MODULE_LICENSE("GPL");

90‫صفحه‬
‫;)">‪MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com‬‬
‫;)"‪MODULE_DESCRIPTION("USB Pen Device Driver‬‬

‫دوباره کارهایی که برای ساخت درایور هست را انجام میدهیم‪:‬‬


‫• ساختن درایور )‪ (pen_driver.ko‬با استفاده از دستور ‪make‬‬
‫• لود کردن درایور با استفاده از دستور ‪insmod pen_driver.ko‬‬
‫• قلم نوری را به دستگاه وصل کنید )بعد از آنکه مطمئن شدید که درایور ‪ usb-storage‬در‬
‫حافظه نیست(‬
‫یشود را چک کنید )صفر نشانگر شماره فرعی‬
‫• مسیر ‪ /dev/pen0‬که بصور ت داینامیک ایجاد م د‌‬
‫درخواست شده است ‪ Dmesg .‬را برای مقادیر سیستم خودتان چک کنید(‬
‫• در صور ت امکان ‪ ،‬در فایل ‪ /dev/pen0‬بنویسید یا بخوانید )شما به احتمال زیاد به دلیل عدم‬
‫استفاده از دستورا ت ‪ SCSI‬قطع ارتباط خواهید شد یا اینکه به خطای ‪ pipe‬برخواهید خورد(‬
‫• قلم نوری را از سیستم جدا کنید و دوباره ‪ /dev/pen0‬را ببینید‬
‫• با استفاده از دستور ‪ rmmod pen_driver‬درایور را از حافظه خارج کنید‬

‫صفحه‪91‬‬
‫لچهاردهم‪:‬آشنایی با مبانی دیسک سخت و پارتیشن‬
‫فص د‌‬
‫اگر دستور ‪ fdisk -l‬را صادر کنیم شکلی شبیه زیر را خواهیم دید‪:‬‬

‫متر و همچنیببن بببه بببایت‬


‫همانطور که مشاهده میکنید اولین خط‪ ،‬سایز هارد دیسک را به فرمت قابل فه د‌‬
‫نمایش میدهد و دومین خط تعداد هدهای منطقی ‪ ،‬تعببداد سببکتورهای منطقببی در هببر قطبباع و تعببداد واقعببی‬
‫سیلندرها در هر دیسک را نمایش میدهد که با هم هندسه هارد را به نمایش میگذارند‪.‬‬
‫تعداد ‪ 255‬هد نمایانگر تعداد صفحا ت یا دیسکها است بطوری که یک هد خواندن‪/‬نوشتن بببرای یببک دیسببک‬
‫مورد نیاز است که شماره های آنهببا ‪ D1،D2,...D255‬میباشببد‪ .‬هببر دیسببک بایببد تعببداد مشببابهی از دوایببر‬
‫ییابد‪ .‬در مثال بببال‬
‫یشود و به درونی ترین خاتمه م د‌‬
‫متحدالمرکز )قطاع( داشته باشد که از بیرونی ترین شروع م د‌‬
‫‪ 60801‬قطاع در هر دیسک وجود داشت که شماره های آنها از ‪ T1‬تا ‪ T60801‬شماره گببذاری شببده اسببت‪.‬‬
‫ل ‪ T2‬از ‪ D1 , D2 , … D255‬را با هم سبیلندر‬
‫کها را سیلندرگویند مث ا ً‬
‫قطاع های هم شماره بروی دیس د‌‬
‫شماره ‪ C2‬می نامند‪ .‬هر قطاع دارای تعداد مشابهی واحد منطقی بنام بخش یا سکتور میباشد که در مثال فببوق‬

‫صفحه‪92‬‬
‫یشوند و معموا ًل حجم آنها ‪ 512‬بایت میباشد‪.‬‬
‫نها نیز با ‪ S1,S2,...S63‬نامگذاری م د‌‬
‫‪ 63‬عدد میباشد که آ د‌‬
‫با استفاده از اطلعا ت فوق و با فرمول زیر میتوانیم حجم واقعی قابل استفاده از دیسک را بدست بیاوریم‪:‬‬

‫)حجم هر سکتور(* )تعداد سکتور در هر قطاع( * )تعداد قطاع در هر دیسک(* )تعداد دیسک(=حجم واقعی قابل استفاده‬

‫که در مثالی که در شکل بال داریم میشود‪:‬‬

‫‪255 * 60801 * 63 * 512 bytes = 500105249280 bytes.‬‬

‫ممکن است عدد شما کمی کمتر از عدد واقعی )عدد ‪ 500107862016‬در مثال ما( باشد و دلیل آن هم ایببن‬
‫است که فرمول ما بایتهای سیلندرهای ناقص را محاسبه نمیکند‪ .‬و علت اصلی آن هم اختلف تکنولوژی امببروز‬
‫یترکه از هد و سیلندر و بخش استفاده میشده است بازمیگردد ‪.‬‬
‫کها با زمانهای قدیم د‌‬
‫ساخت و دیس د‌‬
‫شها و قطاع های منطقی را برمیگرداند و نه لزوما ًا فیزیکببی‪.‬‬
‫نکته اینجاست که خروجی ‪ fdisk‬به ما هدها و بخ د‌‬
‫سوالی که ممکن است مطرح شود این است که اگر دیسکهای امروزی مفاهیم هندسه فیزیکببی قببدیم را ندارنببد‬
‫یشوند؟‬
‫پس چرا هنوز هستند و بصور ت منطقی نمایش داده م د‌‬
‫دلیل اصلی این است که باز هم بتوانیم با مفهوم پارتیشن کار کنیم و همچنان جدول پارتیشن را داشببته باشببیم‬
‫مخصوصا ًا جداول پارتیشن نوع ‪ DOS‬که خیلی رایج بوده و به هندسه ساده فوق نزدیک هستند‪.‬‬
‫فرمول محاسبا ت سایز سیلندر بصور ت زیر میباشد‪:‬‬

‫)‪(255 heads * 63 sectors / track * 512 bytes / sector = 8225280 bytes‬‬

‫که در خط سوم شکل فوق مشاهده میکنیم و در خطوط بعد علمتگببذاری پارتیشببن هببا در ‪unit‬هببا بصببور ت‬
‫سیلندرهای کامل را ملحظه میفرمایید‪.‬‬

‫جداول پارتیشن از نوع ‪DOS‬‬


‫آموختن جداول پارتیشن ‪ DOS‬خیلی مبحث مهمی است ولی قبل از آن باید مفهوم پارتیشن را متببوجه‬
‫ل پارتیشن داشته باشیم؟‬
‫شویم‪ .‬سوالی که ممکن است مطرح شود این است که برای چه ما باید اص ا ً‬
‫نها یک پارتیشببن نامیببده‬
‫یک هارد دیسک میتواند به یک یا چند دیسک منطقی تقسیم شود که هر کدام از آ د‌‬
‫یشود و برای سازماندهی انواع مختلف دادد‌هها مورد استفاده قرار میگیرد‪ .‬برای مثال اطلعا ت سیسببتم عامببل‬
‫م د‌‬

‫صفحه‪93‬‬
‫های مختلف‪ ،‬اطلعا ت کاربران‪ ،‬داده های موقت و غیره‪.‬‬
‫مبندی منطقی برای هارد دیسک‬
‫بنابراین اگر بخواهیم پارتیشن ها را بصور ت ساده بیان کنیم آنها یک نوع تقسی د‌‬
‫هستند‪ .‬اطلعا ت مربوط به پارتیشنها نیز باید جایی نگهداری شود که به آن جدول پارتیشن گویند‪ .‬یک جببدول‬
‫پارتیشن از نوع ‪ DOS‬حداکثر چهار ورودی پارتیشن دارد که هرکدام ‪ 16‬بایت هستند‪ .‬با ساختار به زبببان ‪C‬‬
‫نها را مجسم کرد‪:‬‬
‫زیر میتوان آ د‌‬
‫‪typedef struct‬‬
‫{‬
‫)‪unsigned char boot_type; // 0x00 - Inactive; 0x80 - Active (Bootable‬‬
‫;‪unsigned char start_head‬‬
‫;‪unsigned char start_sec:6‬‬
‫;‪unsigned char start_cyl_hi:2‬‬
‫;‪unsigned char start_cyl‬‬
‫;‪unsigned char part_type‬‬
‫;‪unsigned char end_head‬‬
‫;‪unsigned char end_sec:6‬‬
‫;‪unsigned char end_cyl_hi:2‬‬
‫;‪unsigned char end_cyl‬‬
‫;‪unsigned long abs_start_sec‬‬
‫;‪unsigned long sec_in_part‬‬
‫;‪} PartEntry‬‬

‫این جدول پارتیشن ‪ ،‬پس از یک امضاء دو بایتی ‪ 0xAA55‬در انتهای اولین سکتور دیسک قرار میگیرد‬
‫که معموا ًل با نام رکورد بو ت اصلی )‪ ( MBR‬شناخته میشود‪ .‬بنابراین شروع آفسببت ایببن جببدول پارتیشببن در‬
‫یشود‪ .‬همچنین یک ‪ 4‬بایتی امضاء دیسک در آفست‬
‫داخل ‪ MBR‬بصور ت ‪ 446 = (2 + 16 * 4) - 512‬م د‌‬
‫‪ 440‬قرار میگیرد‪.‬‬
‫‪ 440‬بایت قبل از ‪ MBR‬معموا ًل برای اولین قسمت کد بو ت که توسط بایوس برای راه اندازی سیستم از روی‬
‫دیسک سخت استفاده میگردد‪ .‬سورس فایل ‪ part_info.c‬شامل این تعاریف مختلف برای تجزیه و تحلیل و‬
‫نمایش فرمت بندی شده جدول پارتیشن بروی خروجی نوشته شده است‪.‬‬
‫از مقادیر ساختار جدول پارتیشن‪ ،‬میتوان به فیلدهای شروع و خاتمه سیلندر که فقط ‪ 10‬بیت طول دارند اشبباره‬
‫کردکه حداکثر فقط میتوان ‪ 1023‬سیلندر داشت‪ .‬اما برای هارد دیسکهای حجیببم امببروزی ایببن مقببدار کببافی‬
‫نیست بنابراین در حالتهای اینچنینی ‪ ،‬سه تایی »هد ‪ ،‬سیلندر ‪ ،‬سکتور« در جدول پارتیشن برای حببداکثر مقببدار‬

‫صفحه‪94‬‬
:‫یشود‬
‌‫یشود و مقدار واقعی با استفاده از دو فیلد زیر محاسبه م د‬
‌‫ثبت م د‬
(sec_in_part) ‫( و تعداد سکتور در این پارتیشن‬abs_start_sec) ‫تعداد سکتور شروع‬
:‫ بصور ت زیر است‬part_info.c ‫سورس کد فایل‬

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define SECTOR_SIZE 512


#define MBR_SIZE SECTOR_SIZE
#define MBR_DISK_SIGNATURE_OFFSET 440
#define MBR_DISK_SIGNATURE_SIZE 4
#define PARTITION_TABLE_OFFSET 446
#define PARTITION_ENTRY_SIZE 16 // sizeof(PartEntry)
#define PARTITION_TABLE_SIZE 64 // sizeof(PartTable)
#define MBR_SIGNATURE_OFFSET 510
#define MBR_SIGNATURE_SIZE 2
#define MBR_SIGNATURE 0xAA 55
#define BR_SIZE SECTOR_SIZE
#define BR_SIGNATURE_OFFSET 510
#define BR_SIGNATURE_SIZE 2
#define BR_SIGNATURE 0xAA55

typedef struct {
unsigned char boot_type; // 0x00 - Inactive; 0x80 - Active (Bootable)
unsigned char start_head;
unsigned char start_sec:6;
unsigned char start_cyl_hi:2;
unsigned char start_cyl;
unsigned char part_type;
unsigned char end_head;

95‫صفحه‬
unsigned char end_sec:6;
unsigned char end_cyl_hi:2;
unsigned char end_cyl;
unsigned long abs_start_sec;
unsigned long sec_in_part;
} PartEntry;

typedef struct {
unsigned char boot_code[MBR_DISK_SIGNATURE_OFFSET];
unsigned long disk_signature;
unsigned short pad;
unsigned char pt[PARTITION_TABLE_SIZE];
unsigned short signature;
} MBR;

void print_computed(unsigned long sector) {


unsigned long heads, cyls, tracks, sectors;

sectors = sector % 63 + 1 /* As indexed from 1 */;


tracks = sector / 63;
cyls = tracks / 255 + 1 /* As indexed from 1 */;
heads = tracks % 255;
printf("(%3d/%5d/%1d)", heads, cyls, sectors);
}

int main(int argc, char *argv[]) {


char *dev_file = "/dev/sda";
int fd, i, rd_val;
MBR m;
PartEntry *p = (PartEntry *)(m.pt);

if (argc == 2) {
dev_file = argv[1];
}

96‫صفحه‬
if ((fd = open(dev_file, O_RDONLY)) == -1) {
fprintf(stderr, "Failed opening %s: ", dev_file);
perror("");
return 1;
}
if ((rd_val = read(fd, &m, sizeof(m))) != sizeof(m)) {
fprintf(stderr, "Failed reading %s: ", dev_file);
perror("");
close(fd);
return 2;
}
close(fd);
printf("\nDOS type Partition Table of %s:\n", dev_file);
printf(" B Start (H/C/S) End (H/C/S) Type StartSec TotSec\n");
for (i = 0; i < 4; i++) {
printf("%d:%d (%3d/%4d/%2d) (%3d/%4d/%2d) %02X %10d %9d\n",
i + 1, !!(p[i].boot_type & 0x80),
p[i].start_head,
1 + ((p[i].start_cyl_hi << 8) | p[i].start_cyl),
p[i].start_sec,
p[i].end_head,
1 + ((p[i].end_cyl_hi << 8) | p[i].end_cyl),
p[i].end_sec,
p[i].part_type,
p[i].abs_start_sec, p[i].sec_in_part);
}
printf("\nRe-computed Partition Table of %s:\n", dev_file);
printf(" B Start (H/C/S) End (H/C/S) Type StartSec TotSec\n");
for (i = 0; i < 4; i++) {
printf("%d:%d ", i + 1, !!(p[i].boot_type & 0x80));
print_computed(p[i].abs_start_sec);
printf(" ");
print_computed(p[i].abs_start_sec + p[i].sec_in_part - 1);
printf(" %02X %10d %9d\n", p[i].part_type,

97‫صفحه‬
‫;)‪p[i].abs_start_sec, p[i].sec_in_part‬‬
‫}‬
‫;)"‪printf("\n‬‬
‫;‪return 0‬‬
‫}‬

‫کد بال یک برنامه است که میتوان با ‪ gcc part_info.c -o part_info‬کامپایل کببرد و سببپس بببا‬
‫‪ ./part_info /dev/sda‬آنرا اجرا نمود تا اطلعا ت پارتیشن اصلی تان یعنی ‪ /dev/sda‬را ببینید‪.‬‬
‫شکل زیر خروجی این برنامه را بروی سیستم من نمایش میدهد که میتوانید خروجی آنببرا بببا خروجببی ‪fdisk‬‬
‫ای که در چند صفحه قبل دیدید‪ ،‬مقایسه نمایید‪.‬‬

‫انواع پارتیشن و رکوردهای بوت‬


‫از آنجاییکه جدول پارتیشن طوری طراحی شده )به عبار ت دیگر هاردکد شده( است که تنها ‪ 4‬ورودی‬
‫داشته باشد‪ ،‬و شما حداکثر این تعداد پارتیشن را میتوانید داشته باشید که به آنها پارتیشن های اصببلی گوینببد‪.‬‬
‫ی جدول پارتیشببن مربببوطه دارنببدکه ایببن نببوع هببا معمببوا ًل توسببط‬
‫نها یک نوع ارتباط به ورود ِ‬
‫هر کدام از آ د‌‬

‫صفحه‪98‬‬
‫یشوند و از اینرو براساس سیستم عاملهایی مانند ‪DOS, Minix, Linux,‬‬
‫سازندگان سیستم عاملها ابداع م د‌‬
‫یشوند تا‬
‫‪ Solaris, BSD, FreeBSD, QNX, W95, Novell Netware‬و غیره دسته بندی م د‌‬
‫برای‪/‬با چندین سیستم عامل استفاده شوند‪.‬‬
‫یکی از چهار پارتیشن اصلی میتواند چیز دیگری نامگذاری شود که به آن پارتیشن تعمیم داده شده گوینببد و از‬
‫اهمیت ویژه ای نیز برخوردار است)‪ .(extended partition‬از نامش پیدا است که برای تعمیم بیشببتر‪،‬‬
‫نهبا‬
‫ل تقسیما ت دیسک مورد استفاده قرار میگیرد تا پارتیشنهای بیشتری داشته باشببیم‪.‬ببه عببار ت دیگبر آ د‌‬
‫مث ا ً‬
‫پارتیشنهای منطقی هستند که در درون یک پارتیشن اصلی تعمیم داشته شده اند‪ .‬اطلعا ت این پارتیشن هببا در‬
‫یک لینک لیست حفظ میشود)‪ ( linked-list‬و اجازه میدهد که تعداد بیشماری )حببداقل از نظببر تئوری( از‬
‫پارتیشنهای منطقی داشته باشیم‪.‬‬
‫اولین سکتور پارتیشن تعمیم داده شده معموا ًل رکورد بو ت نامیده میشود)‪ (BR=boot recoard‬که بببرای‬
‫ذخیره پارتیشن های منطقی مورد استفاده قرار میگیرد دقیقا ًا شبیه ‪ MBR‬که برای ذخیره )ابتدای لینک لیست(‬
‫جدول پارتیشن استفاده میگردید‪.‬‬
‫گرد‌ههای بعدی لینک لیست در اولین سکتور پارتیشن های منطقی بعدی که بببه بببو ت رکببورد منطقببی )‪(LBR‬‬
‫اشاره میکند‪ ،‬ذخیره میشوند‪ .‬هر گره از لینک لیست ‪ 4‬مدخل کامل جدول پارتیشببن دارد امببا تنهببا دو مببدخل‬
‫ابتدایی مورد استفاده قرار میگیرد که اولی برای مقدار لینک لیست یعنی اطلعببا ت پارتیشببن منطقببی مجبباور‪ ،‬و‬
‫دومی اشاره گر بعدی لینک لیست که به لیست پارتیشنهای منطقی باقیمانده اشاره میکند‪.‬‬
‫برای مقایسه و فهم بیشتر جزئیا ت پارتیشن اصلی بروی دیسک سخت سیستمتان ‪ ،‬مراحل زیر را انجببام دهیببد‬
‫)البته با دقت و با دسترسی یوزر ‪:(root‬‬

‫‪./part_info /dev/sda ## Displays the partition table on /dev/sda‬‬


‫‪fdisk -l /dev/sda ## To display and compare the partition table entries with the‬‬
‫‪above‬‬

‫الن شما با انتخاب و با دقت با دیسک سخت سیستم خود کلنجار رفتد‌هاید )البته بصور ت فقط خواندنی(‪.‬چرا بببا‬
‫دقت؟ چون در غیر اینصور ت ممکن بود سیستم شما دیگر قابل بو ت نباشد‪ .‬اما هیچ یادگیری بدون اکتشببافاتی‬
‫کامل نمیشود‪ .‬بنابراین در فصل بعدی ما یک دیسک مصنوعی در ‪ ram‬میسازیم و خراب میکنیم‪.‬‬

‫صفحه‪99‬‬
RAM ‫ساخت یک دیسک در حافظه‬:‫لپانزدهم‬
‌‫فص د‬
‫ بسازیم که درونش شش فایل که شامل‬DiskOnRam ‫در این فصل درابتدا میخواهیم شاخه ای بنام‬
:‫ محتوای فایلها به قرار زیر میباشد‬.‫ قراردارد‬Makefile ‫ دو هدر فایل و یک‬C ‫ل برنامه‬
ِ ‫سه فای‬

partition.h
#ifndef PARTITION_H
#define PARTITION_H

#include <linux/types.h>

extern void copy_mbr_n_br(u8 *disk);


#endif

partition.c
#include <linux/string.h>
#include "partition.h"

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*a))

#define SECTOR_SIZE 512


#define MBR_SIZE SECTOR_SIZE
#define MBR_DISK_SIGNATURE_OFFSET 440
#define MBR_DISK_SIGNATURE_SIZE 4
#define PARTITION_TABLE_OFFSET 446
#define PARTITION_ENTRY_SIZE 16 // sizeof(PartEntry)
#define PARTITION_TABLE_SIZE 64 // sizeof(PartTable)
#define MBR_SIGNATURE_OFFSET 510
#define MBR_SIGNATURE_SIZE 2

100‫صفحه‬
#define MBR_SIGNATURE 0xAA55
#define BR_SIZE SECTOR_SIZE
#define BR_SIGNATURE_OFFSET 510
#define BR_SIGNATURE_SIZE 2
#define BR_SIGNATURE 0xAA55

typedef struct
{
unsigned char boot_type; // 0x00 - Inactive; 0x80 - Active (Bootable)
unsigned char start_head;
unsigned char start_sec:6;
unsigned char start_cyl_hi:2;
unsigned char start_cyl;
unsigned char part_type;
unsigned char end_head;
unsigned char end_sec:6;
unsigned char end_cyl_hi:2;
unsigned char end_cyl;
unsigned long abs_start_sec;
unsigned long sec_in_part;
} PartEntry;

typedef PartEntry PartTable[4];

static PartTable def_part_table =


{
{
boot_type: 0x00,
start_head: 0x00,

101‫صفحه‬
start_sec: 0x2,
start_cyl: 0x00,
part_type: 0x83,
end_head: 0x00,
end_sec: 0x20,
end_cyl: 0x09,
abs_start_sec: 0x00000001,
sec_in_part: 0x0000013F
},
{
boot_type: 0x00,
start_head: 0x00,
start_sec: 0x1,
start_cyl: 0x0A, // extended partition start cylinder (BR
location)
part_type: 0x05,
end_head: 0x00,
end_sec: 0x20,
end_cyl: 0x13,
abs_start_sec: 0x00000140,
sec_in_part: 0x00000140
},
{
boot_type: 0x00,
start_head: 0x00,
start_sec: 0x1,
start_cyl: 0x14,
part_type: 0x83,
end_head: 0x00,

102‫صفحه‬
end_sec: 0x20,
end_cyl: 0x1F,
abs_start_sec: 0x00000280,
sec_in_part: 0x00000180
},
{
}
};
static unsigned int def_log_part_br_cyl[] = {0x0A, 0x0E, 0x12};
static const PartTable def_log_part_table[] =
{
{
{
boot_type: 0x00,
start_head: 0x00,
start_sec: 0x2,
start_cyl: 0x0A,
part_type: 0x83,
end_head: 0x00,
end_sec: 0x20,
end_cyl: 0x0D,
abs_start_sec: 0x00000001,
sec_in_part: 0x0000007F
},
{
boot_type: 0x00,
start_head: 0x00,
start_sec: 0x1,
start_cyl: 0x0E,

103‫صفحه‬
part_type: 0x05,
end_head: 0x00,
end_sec: 0x20,
end_cyl: 0x11,
abs_start_sec: 0x00000080,
sec_in_part: 0x00000080
},
},
{
{
boot_type: 0x00,
start_head: 0x00,
start_sec: 0x2,
start_cyl: 0x0E,
part_type: 0x83,
end_head: 0x00,
end_sec: 0x20,
end_cyl: 0x11,
abs_start_sec: 0x00000001,
sec_in_part: 0x0000007F
},
{
boot_type: 0x00,
start_head: 0x00,
start_sec: 0x1,
start_cyl: 0x12,
part_type: 0x05,
end_head: 0x00,
end_sec: 0x20,

104‫صفحه‬
end_cyl: 0x13,
abs_start_sec: 0x00000100,
sec_in_part: 0x00000040
},
},
{
{
boot_type: 0x00,
start_head: 0x00,
start_sec: 0x2,
start_cyl: 0x12,
part_type: 0x83,
end_head: 0x00,
end_sec: 0x20,
end_cyl: 0x13,
abs_start_sec: 0x00000001,
sec_in_part: 0x0000003F
},
}
};

static void copy_mbr(u8 *disk)


{
memset(disk, 0x0, MBR_SIZE);
*(unsigned long *)(disk + MBR_DISK_SIGNATURE_OFFSET) =
0x36E5756D;
memcpy(disk + PARTITION_TABLE_OFFSET, &def_part_table,
PARTITION_TABLE_SIZE);
*(unsigned short *)(disk + MBR_SIGNATURE_OFFSET) =

105‫صفحه‬
MBR_SIGNATURE;
}
static void copy_br(u8 *disk, int start_cylinder, const PartTable
*part_table)
{
disk += (start_cylinder * 32 /* sectors / cyl */ * SECTOR_SIZE);
memset(disk, 0x0, BR_SIZE);
memcpy(disk + PARTITION_TABLE_OFFSET, part_table,
PARTITION_TABLE_SIZE);
*(unsigned short *)(disk + BR_SIGNATURE_OFFSET) =
BR_SIGNATURE;
}
void copy_mbr_n_br(u8 *disk)
{
int i;

copy_mbr(disk);
for (i = 0; i < ARRAY_SIZE(def_log_part_table); i++)
{
copy_br(disk, def_log_part_br_cyl[i], &def_log_part_table[i]);
}
}

ram_device.h

#ifndef RAMDEVICE_H
#define RAMDEVICE_H

#define RB_SECTOR_SIZE 512

106‫صفحه‬
extern int ramdevice_init(void);
extern void ramdevice_cleanup(void);
extern void ramdevice_write(sector_t sector_off, u8 *buffer, unsigned int sectors);
extern void ramdevice_read(sector_t sector_off, u8 *buffer, unsigned int sectors);
#endif

ram_device.c
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/string.h>

#include "ram_device.h"
#include "partition.h"

#define RB_DEVICE_SIZE 1024 /* sectors */


/* So, total device size = 1024 * 512 bytes = 512 KiB */

/* Array where the disk stores its data */


static u8 *dev_data;

int ramdevice_init(void)
{
dev_data = vmalloc(RB_DEVICE_SIZE * RB_SECTOR_SIZE);
if (dev_data == NULL)
return -ENOMEM;
/* Setup its partition table */
copy_mbr_n_br(dev_data);
return RB_DEVICE_SIZE;
}

void ramdevice_cleanup(void)
{

107‫صفحه‬
vfree(dev_data);
}

void ramdevice_write(sector_t sector_off, u8 *buffer, unsigned int sectors)


{
memcpy(dev_data + sector_off * RB_SECTOR_SIZE, buffer,
sectors * RB_SECTOR_SIZE);
}
void ramdevice_read(sector_t sector_off, u8 *buffer, unsigned int sectors)
{
memcpy(buffer, dev_data + sector_off * RB_SECTOR_SIZE,
sectors * RB_SECTOR_SIZE);
}

ram_block.c
/* Disk on RAM Driver */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/errno.h>

#include "ram_device.h"

#define RB_FIRST_MINOR 0
#define RB_MINOR_CNT 16

static u_int rb_major = 0;

/*
* The internal structure representation of our Device
*/

108‫صفحه‬
static struct rb_device
{
/* Size is the size of the device (in sectors) */
unsigned int size;
/* For exclusive access to our request queue */
spinlock_t lock;
/* Our request queue */
struct request_queue *rb_queue;
/* This is kernel's representation of an individual disk device */
struct gendisk *rb_disk;
} rb_dev;

static int rb_open(struct block_device *bdev, fmode_t mode)


{
unsigned unit = iminor(bdev->bd_inode);

printk(KERN_INFO "rb: Device is opened\n");


printk(KERN_INFO "rb: Inode number is %d\n", unit);

if (unit > RB_MINOR_CNT)


return -ENODEV;
return 0;
}

static int rb_close(struct gendisk *disk, fmode_t mode)


{
printk(KERN_INFO "rb: Device is closed\n");
return 0;
}

/*
* Actual Data transfer
*/
static int rb_transfer(struct request *req)

109‫صفحه‬
{
//struct rb_device *dev = (struct rb_device *)(req->rq_disk->private_data);

int dir = rq_data_dir(req);


sector_t start_sector = blk_rq_pos(req);
unsigned int sector_cnt = blk_rq_sectors(req);

struct bio_vec *bv;


struct req_iterator iter;

sector_t sector_offset;
unsigned int sectors;
u8 *buffer;

int ret = 0;

//printk(KERN_DEBUG "rb: Dir:%d; Sec:%lld; Cnt:%d\n", dir, start_sector,


sector_cnt);

sector_offset = 0;
rq_for_each_segment(bv, req, iter)
{
buffer = page_address(bv->bv_page) + bv->bv_offset;
if (bv->bv_len % RB_SECTOR_SIZE != 0)
{
printk(KERN_ERR "rb: Should never happen: "
"bio size (%d) is not a multiple of RB_SECTOR_SIZE (%d).\n"
"This may lead to data truncation.\n",
bv->bv_len, RB_SECTOR_SIZE);
ret = -EIO;
}
sectors = bv->bv_len / RB_SECTOR_SIZE;
printk(KERN_DEBUG "rb: Sector Offset: %lld; Buffer: %p; Length: %d
sectors\n",

110‫صفحه‬
sector_offset, buffer, sectors);
if (dir == WRITE) /* Write to the device */
{
ramdevice_write(start_sector + sector_offset, buffer, sectors);
}
else /* Read from the device */
{
ramdevice_read(start_sector + sector_offset, buffer, sectors);
}
sector_offset += sectors;
}
if (sector_offset != sector_cnt)
{
printk(KERN_ERR "rb: bio info doesn't match with the request info");
ret = -EIO;
}

return ret;
}

/*
* Represents a block I/O request for us to execute
*/
static void rb_request(struct request_queue *q)
{
struct request *req;
int ret;

/* Gets the current request from the dispatch queue */


while ((req = blk_fetch_request(q)) != NULL)
{
#if 0
/*
* This function tells us whether we are looking at a filesystem request

111‫صفحه‬
* - one that moves block of data
*/
if (!blk_fs_request(req))
{
printk(KERN_NOTICE "rb: Skip non-fs request\n");
/* We pass 0 to indicate that we successfully completed the request
*/
__blk_end_request_all(req, 0);
//__blk_end_request(req, 0, blk_rq_bytes(req));
continue;
}
#endif
ret = rb_transfer(req);
__blk_end_request_all(req, ret);
//__blk_end_request(req, ret, blk_rq_bytes(req));
}
}

/*
* These are the file operations that performed on the ram block device
*/
static struct block_device_operations rb_fops =
{
.owner = THIS_MODULE,
.open = rb_open,
.release = rb_close,
};

/*
* This is the registration and initialization section of the ram block device
* driver
*/
static int __init rb_init(void)
{

112‫صفحه‬
int ret;

/* Set up our RAM Device */


if ((ret = ramdevice_init()) < 0)
{
return ret;
}
rb_dev.size = ret;

/* Get Registered */
rb_major = register_blkdev(rb_major, "rb");
if (rb_major <= 0)
{
printk(KERN_ERR "rb: Unable to get Major Number\n");
ramdevice_cleanup();
return -EBUSY;
}

/* Get a request queue (here queue is created) */


spin_lock_init(&rb_dev.lock);
rb_dev.rb_queue = blk_init_queue(rb_request, &rb_dev.lock);
if (rb_dev.rb_queue == NULL)
{
printk(KERN_ERR "rb: blk_init_queue failure\n");
unregister_blkdev(rb_major, "rb");
ramdevice_cleanup();
return -ENOMEM;
}

/*
* Add the gendisk structure
* By using this memory allocation is involved,
* the minor number we need to pass bcz the device
* will support this much partitions

113‫صفحه‬
*/
rb_dev.rb_disk = alloc_disk(RB_MINOR_CNT);
if (!rb_dev.rb_disk)
{
printk(KERN_ERR "rb: alloc_disk failure\n");
blk_cleanup_queue(rb_dev.rb_queue);
unregister_blkdev(rb_major, "rb");
ramdevice_cleanup();
return -ENOMEM;
}

/* Setting the major number */


rb_dev.rb_disk->major = rb_major;
/* Setting the first mior number */
rb_dev.rb_disk->first_minor = RB_FIRST_MINOR;
/* Initializing the device operations */
rb_dev.rb_disk->fops = &rb_fops;
/* Driver-specific own internal data */
rb_dev.rb_disk->private_data = &rb_dev;
rb_dev.rb_disk->queue = rb_dev.rb_queue;
/*
* You do not want partition information to show up in
* cat /proc/partitions set this flags
*/
//rb_dev.rb_disk->flags = GENHD_FL_SUPPRESS_PARTITION_INFO;
sprintf(rb_dev.rb_disk->disk_name, "rb");
/* Setting the capacity of the device in its gendisk structure */
set_capacity(rb_dev.rb_disk, rb_dev.size);

/* Adding the disk to the system */


add_disk(rb_dev.rb_disk);
/* Now the disk is "live" */
printk(KERN_INFO "rb: Ram Block driver initialised (%d sectors; %d bytes)\n",
rb_dev.size, rb_dev.size * RB_SECTOR_SIZE);

114‫صفحه‬
return 0;
}
/*
* This is the unregistration and uninitialization section of the ram block
* device driver
*/
static void __exit rb_cleanup(void)
{
del_gendisk(rb_dev.rb_disk);
put_disk(rb_dev.rb_disk);
blk_cleanup_queue(rb_dev.rb_queue);
unregister_blkdev(rb_major, "rb");
ramdevice_cleanup();
}

module_init(rb_init);
module_exit(rb_cleanup);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <[email protected]>");
MODULE_DESCRIPTION("Ram Block Driver");
MODULE_ALIAS_BLOCKDEV_MAJOR(rb_major);

‫( )بببا‬dor.ko)‫ بنویسید و اجرا کنید‬ram ‫ را برای ساخت درایور دیسک در حافظه‬make ‫مانند گذشته کد‬
( c ‫ترکیب به فایل‬
:‫ را میتوان بصور ت زیر نوشت‬Makefile

Makefile
# If called directly from the command line, invoke the kernel build system.
ifeq ($(KERNELRELEASE),)
KERNEL_SOURCE := /usr/src/linux
PWD := $(shell pwd)

115‫صفحه‬
default: module

module:
$(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) modules
clean:
$(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) clean

# Otherwise KERNELRELEASE is defined; we've been invoked from the


# kernel build system and can use its language.
else
obj-m := dor.o
dor-y := ram_block.o ram_device.o partition.o
endif

.‫ را طبق معمول اجرا کنید‬make clean ‫برای حذف فایل اجرایی ساخته شده نیز میتوانید دستور‬
:‫تجربه اجرای درایور فوق در سیستم من را بصور ت شکلهای زیر مشاهده میفرمایید‬

116‫صفحه‬
‫صفحه‪117‬‬
‫صفحه‪118‬‬
‫توجه داشته باشید که تمام مراحل زیر میبایست با مجوز ‪ root‬اجرا گردد‪:‬‬
‫• لود کردن درایور ‪ dor.ko‬در کرنل با استفاده از دستور ‪ . insmod‬این دستور میبایست یک فایببل‬
‫دستگاه بلکی را ایجاد کند که نمایانگر دیسکی با ظرفیت ‪ 512‬کیلوبایت در حببافظه ‪ ram‬اسببت کببه‬
‫حاوی سه پارتیشن اصلی و سه پارتیشن منطقی میباشد‪.‬‬
‫یشوند که ‪ /dev/rb‬کببل دیسببک‬
‫• فایلهای دستگاه بلوکی )*‪ (/dev/rb/‬بصور ت اتوماتیک ساخته م د‌‬
‫است که ‪ 512‬کیلوبایت حجم دارد‪ rb1.‬و ‪ rb2‬و ‪ rb3‬سه پارتیشن اصلی اسببت کببه ‪ rb2‬پارتیشببن‬
‫توسعه داده شده میباشد و خود دارای سه پارتیشن منطقی ‪ rb5‬و ‪ rb6‬و ‪ rb7‬نیز میباشد‪.‬‬
‫• با استفاده از دستور ‪ dd‬کل دیسک را میتوانید بخوانید‪.‬‬
‫• اولین سکتور از اولین پارتیشن دیسک )‪ (/dev/rb1‬را با صفر پرکرده و دوببباره دسببتور ‪ dd‬را اجببرا‬
‫کنید‬
‫• با استفاده از دستور ‪ cat‬متنی را در اولین پارتیشن )‪ (/dev/rb1‬بنویسید‬
‫• با استفاده از دستور ‪ xxd‬محتوی اولیه اولین پارتیشن )‪ (/dev/rb1‬را ببینید‬
‫• با استفاده از دستور ‪ fdisk‬اطلعا ت پارتیشن را ببینید‬

‫صفحه‪119‬‬
‫• با استفاده از ‪ mkfs.vfat‬سبومین پارتیشبن اصبلی )‪ (/dev/rb3‬را از نبوع فایبل سیسبتم ‪vfat‬‬
‫فرمت بندی سریع کنید)شبیه کاری که برای درایور قلم نوری انجام دادیم(‬
‫• با استفاده از دستور ‪ ، mount‬پارتیشن فرمت شده جدید را در ‪ /mnt‬مونت )‪ (mount‬کنید‪.‬‬
‫• با استفاده از دستور ‪ df‬حجم پارتیشن مونت شده را ببینید‪ .‬شما ممکن حتی از این هم فراتببر برویببد و‬
‫فایلی را در این پارتیشن ذخیره کنید ولی یادتان باشیدکه این دیسک در حافظه ‪ RAM‬است و دائمببی‬
‫نمیباشد‪.‬‬
‫• بعد از آنکه با استفاده از دستور ‪ umount /mnt‬پارتیشن را غیر فعال کردید با استفاده از دستور‬
‫‪ rmmod dor‬درایور را از کرنل حذف کنید‪ .‬با اینکار کل اطلعا ت ذخیره شده در این دیسک از‬
‫دست خواهد رفت‪.‬‬

‫حال بیایید قوانین کار را بیاموزیم‪:‬‬


‫ما بدون اینکه قوانین این بازی را بدانیم فقط دیسکی را در حافظه ‪ RAM‬ساختیم و کمی هم بببا آن کببار‬
‫کردیم‪ .‬حال بیایید در مورد این درایور کنکاشببی داشببته باشببیم‪ .‬هببر یببک از سببه فایببل *‪ c.‬نمایببانگر قسببمت‬
‫مخصوصببی از درایببور اسببت‪ ram_device.c.‬و ‪ ram_device.h‬عملیببا ت اساسببی ‪ ram‬شبببیه‬
‫‪ vmalloc/vfree‬و ‪ memcpy‬و‪ ...‬را تعریف میکند که ‪ API‬هایی برای عملیاتی شبیه شروع‪/‬خبباتمه‬
‫و خواندن‪/‬نوشتن و … را ممکن سازد‪.‬‬
‫نها جداول پارتیشببن مختلببف را میسببازد و‬
‫‪ partition.c‬و ‪ partition.h‬توابعی است که با استفاده از آ د‌‬
‫تعریف میکند‪ .‬برای جزئیا ت مفاهیم پارتیشن میتوانید فصل قبل را دوباره و با دقت بخوانید‪ .‬کدهای ایببن فایببل‬
‫یشود را بببر‬
‫مسئولیت اطلعا ت پارتیشن ها از قبیل تعداد ‪ ،‬نوع ‪ ،‬حجم و غیره که توسط ‪ fdisk‬نمایش داده م د‌‬
‫عهده دارد‪.‬‬
‫فایل ‪ ram_block.c‬کار پیادد‌هسازی هستد‌هی درایور بلکی را انجام میدهد کببه درایببور ‪ DOR‬را در سببطح‬
‫فضای یوزر و توسط فایلهای دستگاه بلوکی )*‪ (/dev/rb/‬نمایان میکند‪ .‬به عبار ت دیگببر چهببار فایببل از پنببج‬
‫فایل *‪ ram_device.‬و *‪ partition.‬از لیه افقی و ‪ ram_block.c‬از لیه عمودی درایببور دسببتگاه‬
‫میباشد‪.‬‬

‫مقدمات درایور بلکی‬


‫درایورهای بلکی بصور ت مفهومی بسیار شبیه درایورهای کاراکتری هستند مخصوصا ًا اینکه‪:‬‬
‫• از فایلهای دستگاه بلکی استفاده میکنند‬
‫• دارای شماره های اصلی و فرعی میباشند‬

‫صفحه‪120‬‬
‫• عملیا ت فایلی بروی فایلهای دستگاه میسر میباشد‬
‫نها وجود دارد‬
‫• مفهوم ثبت دستگاه نیز در آ د‌‬

‫تتر میتببوانیم درایببور هببای بلکببی را نیببز‬


‫بدین ترتیب اگر پیادد‌هسازی دستگاه کاراکتری را میدانیم راح د‌‬
‫بفهمیم‪.‬‬
‫نها دقیقا ًا یکسان نیستند و تفاوتهای کلیدی نیز باهم دارند که در ذیل آمده است‪:‬‬
‫اما آ د‌‬
‫دستگاه بل ک گرا در مقابل دستگاه بایت گرا‬ ‫•‬
‫دستکاههای بلکی برای ‪ I/O‬های زیاد برنامد‌هریزی شده و برای کببارایی بهینببه طراحببی شببدد‌هاند‪ .‬کببه‬ ‫•‬
‫نها را با دستگاههای کاراکتری که از ‪ VFS‬استفاده میکنند مقایسه کنید‬
‫میتوانید آ د‌‬
‫دستگاههای بلکی برای یکپارچه شدن با مکانیزم بافرکش لینوکس که دسترسی به اطلعا ت را کبباراتر‬ ‫•‬
‫میکند طراحی گردیده است‪ .‬درایورهببای کبباراکتری درایورهببایی مسببتقیم هسببتند یعنببی مسببتقیما ًا بببا‬
‫تافزار دسترسی دارند‪.‬‬
‫سخ د‌‬

‫به دلیل فوق پیادد‌هسازی این دو نوع درایور نیز با هم متفاو ت است‪ .‬حال بیایید با هم قطعاتی از کببدهای‬
‫اصلی ‪ ram_block.c‬را آنالیز کنیم‪ .‬برای شروع از سازنده درایور )(‪ rb_init‬شروع میکنیم‪.‬‬
‫اولین گام ثبت ‪ 8‬بیت )بل ک( شماره اصلی است‪) .‬که بطور ضمنی به معنای ثبت همببه ‪ 256‬شببماره فرعببی ‪8‬‬
‫بیتی به همراه آن است( تابع زیر این کار را انجام میدهد‪:‬‬

‫;)‪int register_blkdev(unsigned int major, const char *name‬‬

‫در اینجا ‪ major‬شببمار اصببلی اسببت کببه ثبببت شببده اسببت و ‪ name‬نببامی اسببت کببه در زیببر شبباخه‬
‫یشود‪ .‬جالب توجه اینکه تابع )(‪ register_blkdev‬سعی میکنببد تببا‬
‫‪ /proc/devices‬نمایش داده م د‌‬
‫یشود‪ ،‬در حالتی کببه‬
‫یک شماره اصلی خالی را بگیرد و ثبت کند‪ .‬وقتی ‪ 0‬برای اولین پارامتر ‪ major‬ارسال م د‌‬
‫موفقیت آمیز باشد عدد در نظر گرفته شده بازگردانده میشود‪ .‬تابع معکوس ثبت نیز متناظر با تببابع ثبببت در‬
‫زیر آمده است‪:‬‬

‫;)‪void unregister_blkdev(unsigned int major, const char *name‬‬

‫که هر دو تابع در هدر فایل >‪ <linux/fs.h‬معرفی شده اند‪.‬‬

‫صفحه‪121‬‬
‫گام دوم تعریف عملیا ت فایل است که از طریق ساختار ‪ ) block_device_operations‬در هدر فایل‬
‫لهای دستگاه ثبت انجام میگیرد‪.‬‬
‫>‪ <linux/blkdev.h‬معرفی گردیده است( برای شماره اصلی فای د‌‬
‫اما این عملیا ت در مقایسه با عملیا ت فایل دستگاد‌ههای کاراکتری چندین برابر کمتر میباشد و معمببوا ًل کوچببک‬
‫نیز هستند‪ .‬حتی بصور ت حرفد‌های نیز هیچ عملیاتی )حتی برای خواندن و نوشتن( وجود نداردکه شببگفت انگیببز‬
‫است ‪.‬دلیل آن این است که درایورهای بلکی با ‪ I/O‬زمانبدی شده یکپارچه شدد‌هاند و پیادد‌هسببازی خوانببدن و‬
‫نوشتن از طریق درخواست فراخوانی از صف بدست می آید‪ .‬همراه با انجام عملیا ت فایل دستگاه باید کارهببای‬
‫زیر انجام شود‪:‬‬
‫در خواست صف برای مرتب کردن درخواستهای خواندن‪/‬نوشتن‬ ‫•‬
‫ف درخواست مربوطه‬
‫ایجاد یک قفل )‪ (lock‬برای جلوگیری از دسترسی همزمان به ص ِ‬ ‫•‬
‫تها در صف درخواست‬
‫ایجاد تابع درخواست برای پردازش درخواس د‌‬ ‫•‬

‫همچنین اینترفیس مجزایی برای ساخت فایلهای بلکی نیز وجود ندارد‪ .‬بنابراین کارهای زیر انجام میشود‪:‬‬
‫پیشوند نام فایل دستگاه که معموا ًل به ‪ disk_name‬باز میگردد )‪ rb‬در درایور ‪(dor‬‬ ‫•‬
‫رشته عدد فرعی برای فایلهای دستگاه که معموا ًل به ‪ first_minor‬باز میگردد‪.‬‬ ‫•‬

‫در نهایت دو مشخصه دستگاه بلکی بصور ت زیر محیا میگردد‪:‬‬


‫حداکثر تعداد پارتیشنهای پشتیانی شده توسط دستگاه بلکببی کببه بوسببیله جمببع شببماره هببای فرعببی‬ ‫•‬
‫مشخص شده است‪.‬‬
‫ کها‪.‬‬
‫مبندی سایز دستگاه در سکتورهایی ‪ 512‬بایتی برای دستیابی منطقی به بل د‌‬
‫تقسی د‌‬ ‫•‬

‫تمام این چیزها در ساختار ‪ gendisk‬در تابع زیر تعریف شدد‌هاند‪:‬‬

‫;)‪void add_disk(struct gendisk *disk‬‬

‫تایع ‪ delete‬مرتبط با آن در خط زیر آمده است‪:‬‬

‫;)‪void del_gendisk(struct gendisk *disk‬‬

‫در )(‪ add_disk‬قبلی چندیدن فیلد ساختار ‪ gendisk‬هست که مستقیما ًا یببا بببا اسببتفاده از ماکروهببا یببا‬

‫صفحه‪122‬‬
‫عای شبببیه ‪ set_capacity().major,first_minor,fops,ueue,disk_name‬حببداقل‬
‫تببواب د‌‬
‫فیلدهای مورد نیاز آن باید مستقیما ًا مقدار دهی شوند‪ .‬قبل از مقدار دهی این فیلدها‪ ،‬ساختار ‪ gendisk‬باید‬
‫حافظه را در اختیار بگیرید که توسط تابع زیر اینکار انجام میشود‪:‬‬

‫;)‪struct gendisk *alloc_disk(int minors‬‬

‫که ‪ minors‬تعداد کل پارتیشن ها برای این دیسک میباشد‪ ،‬برعکس تابع فوق نیز در زیر آمده است‪:‬‬

‫;)‪void put_disk(struct gendisk *disk‬‬

‫نها در فایل >‪ <linux/genhd.h‬معرفی شده اند‪.‬‬


‫که تمام ای د‌‬

‫صف درخواست و تابع درخواست‬


‫صف درخواسببت نیببز نیبباز دارد تببا مقببدار دهببی شببود و در سبباختار ‪ gendisk‬و قبببل از )(‪add_disk‬‬
‫جاگذاری شود‪ .‬صف درخواست بوسیله فراخوانی تابع زیر مقدار دهی میشود‪:‬‬

‫;)* ‪struct request_queue *blk_init_queue(request_fn_proc *, spinlock_t‬‬

‫ما تابع پردازش درخواست و راه اندازی قفل )‪ (lock‬و محافظت از دسترسی موازی را در پارامتر گنجاندیم‪.‬‬
‫تابع پا ک کردن صف مربوطه را در زیر مشاهده میکنید‪:‬‬

‫;)* ‪void blk_cleanup_queue(struct request_queue‬‬

‫تابع درخواست )پردازش( باید طبق زیر تعریف شود‪:‬‬

‫;)‪void request_fn(struct request_queue *q‬‬

‫کد آن باید یک درخواست را از پارامتر ‪ q‬واکشی کند که ما از کد زیر استفاده میکنم‪:‬‬

‫;)‪struct request *blk_fetch_request(struct request_queue *q‬‬

‫صفحه‪123‬‬
‫سپس یا باید پردازش شود یا اینکه یک پردازش را شروع کند‪ .‬و همه چیز به جز بل ک کببردن از یببک تببابع و‬
‫یشود‪ .‬بعلوه توابعی که صببف را قفببل و یببا آزاد‬
‫پس از قفل کردن صف‪ ،‬درخواست غیر فرآیندی فراخوانی م د‌‬
‫یکند باید در تابع درخواست استفاده گردد‪.‬‬
‫نم د‌‬
‫یک مثال معمولی فرآیند درخواست بوسیله تابع )(‪ rb_request‬در فایل ‪ ram_block.c‬مببورد نمببایش‬
‫قرار گرفته است‪:‬‬

‫‪while ((req = blk_fetch_request(q)) != NULL) /* Fetching a request */‬‬


‫{‬

‫‪/* Processing the request: the actual data transfer */‬‬

‫‪ret = rb_transfer(req); /* Our custom function */‬‬

‫‪/* Informing that the request has been processed with return of ret */‬‬

‫;)‪__blk_end_request_all(req, ret‬‬

‫}‬

‫درخواستها و پردازش آنها‬


‫تابع کلیدی ما )(‪ rb_transfer‬هست که سبباختار ‪ request‬را بررسببی میکنببد و برایببن اسبباس‬
‫ت انتقال داده داده ‪ ،‬سببکتو ِر شببروع بببرای‬
‫دادد‌ههای واقعی را منتقل میکند ‪.‬ساختار اولیه ‪ request‬شامل جه ِ‬
‫انتقال داده ‪ ،‬جمع تعداد سکتور برای انتقال ‪ ،‬بافر فرستنده‪/‬گیرنده انتقال داده است‪ .‬چندین ماکرو برای واکشببی‬
‫از ساختار ‪ request‬در زیر لیست شده است‪:‬‬

‫‪rq_data_dir(req); /* Operation type: 0 - read from device; otherwise - write to‬‬


‫‪device */‬‬
‫‪blk_req_pos(req); /* Starting sector to process */‬‬

‫‪blk_req_sectors(req); /* Total sectors to process */‬‬

‫‪rq_for_each_segment(bv, req, iter) /* Iterator to extract individual buffers */‬‬

‫)(‪ rq_for_each_segment‬مخصوص این است که با استفاده از ‪ iter‬در ساختار )‪request (req‬‬

‫صفحه‪124‬‬
‫چرخش کند و اطلعا ت بافر منحصببر بفببرد را در سبباختار ‪bio_vec (bv: basic input/output‬‬
‫‪ ( vector‬در هر چرخش استخراج کند و سپس در هر استخراج دادد‌ههای مناسب‪ ،‬بسببته بببه نببوع عملیببا ت‬
‫منتقل شوند که با استفاده از فراخوانی ‪ API‬های زیر در فایل ‪ ram_device.c‬اینکار انجام میشود‪:‬‬

‫;)‪void ramdevice_write(sector_t sector_off, u8 *buffer, unsigned int sectors‬‬


‫;)‪void ramdevice_read(sector_t sector_off, u8 *buffer, unsigned int sectors‬‬

‫در پایان پیشنهاد میگردد تمام کد )(‪ rb_transfer‬در فایل ‪ ram_block.c‬را حتما ًا بررسی نمایید‪.‬‬

‫صفحه‪125‬‬
‫لشانزدهم‪:‬کاوش در کرنل و آشنایی با ‪/proc‬‬
‫فص د‌‬
‫در یک نگاه به درون پوشه ‪ /proc‬هر زیر شاخه اطلعا ت زیر را به ما میدهد‪:‬‬
‫• ‪ /proc/modules‬ماژول هایی که بصور ت پویا بار گذاری شدد‌هاند‬
‫• ‪ /proc/devices‬شماره اصلی دستگاد‌ههای بلکی و کاراکتری ثبت شده‬
‫سهای باس دستگاه‬
‫• ‪ /proc/iomem‬حافظه اصلی و آدر د‌‬
‫• ‪ /proc/ioports‬آدرس پورتها ورودی‪/‬خروجی بروی سیستم‬
‫• ‪ /proc/interrupts‬شماره های وقفه های ثبت شده‬
‫• ‪ /proc/softirqs‬نمایانگر ‪ IRQ‬های ثبت شده است‬
‫لهای لود شده‬
‫ل در حال اجرا به همراه ماژو د‌‬
‫لهای کرن ِ‬
‫• ‪ /proc/kallsyms‬سیمب د‌‬
‫نهایشان‬
‫• ‪ /proc/partitions‬دستگاههای بلکی متصل شده به همراه پارتیش د‌‬
‫• ‪ /proc/filesystems‬درایور های فایل سیستم فعال فعلی‬
‫• ‪ /proc/swaps‬نمایانگر ‪ swap‬های فعال فعلی است‬
‫• ‪ /proc/cpuinfo‬اطلعاتی در مورد سی پی یو )ها(ی سیستم‬
‫• ‪ /proc/meminfo‬اطلعاتی در مورد حافظه اصلی بروی سیستم‬

‫نمایی سنتی از کرنل‬


‫شناختن کرنل کمک زیادی به ما در جهت فهمیدن و رفع عیب درایورهای دستگاه لینوکس میکند‪.‬اما آیا‬
‫ما میتوانیم یک شناختی نیز از ‪ / proc‬داشته باشیم؟ جواب مثبت است و آنهببم فقببط بببه سببادگی اسببتفاده از‬
‫چندین ‪ API‬است‪ .‬در زیر برنامد‌های نمونه را جهت کار با ‪ /proc‬مشاهده میکنید‪:‬‬

‫>‪#include <linux/module.h‬‬
‫>‪#include <linux/kernel.h‬‬
‫>‪#include <linux/proc_fs.h‬‬
‫>‪#include <linux/jiffies.h‬‬

‫;‪static struct proc_dir_entry *parent, *file, *link‬‬


‫;‪static int state = 0‬‬

‫صفحه‪126‬‬
int time_read(char *page, char **start, off_t off, int count, int *eof, void *data) {
int len, val;
unsigned long act_jiffies;

len = sprintf(page, "state = %d\n", state);


act_jiffies = jiffies - INITIAL_JIFFIES;
val = jiffies_to_msecs(act_jiffies);
switch (state) {
case 0:
len += sprintf(page + len, "time = %ld jiffies\n", act_jiffies);
break;
case 1:
len += sprintf(page + len, "time = %d msecs\n", val);
break;
case 2:
len += sprintf(page + len, "time = %ds %dms\n",
val / 1000, val % 1000);
break;
case 3:
val /= 1000;
len += sprintf(page + len, "time = %02d:%02d:%02d\n",
val / 3600, (val / 60) % 60, val % 60);
break;
default:
len += sprintf(page + len, "<not implemented>\n");
break;
}
len += sprintf(page + len, "{offset = %ld; count = %d;}\n", off, count);

return len;
}
int time_write(struct file *file, const char __user *buffer, unsigned long count, void
*data) {
if (count > 2)

127‫صفحه‬
return count;
if ((count == 2) && (buffer[1] != '\n'))
return count;
if ((buffer[0] < '0') || ('9' < buffer[0]))
return count;
state = buffer[0] - '0';
return count;
}

static int __init proc_win_init(void) {


if ((parent = proc_mkdir("anil", NULL)) == NULL) {
return -1;
}
if ((file = create_proc_entry("rel_time", 0666, parent)) == NULL) {
remove_proc_entry("anil", NULL);
return -1;
}
file->read_proc = time_read;
file->write_proc = time_write;
if ((link = proc_symlink("rel_time_l", parent, "rel_time")) == NULL) {
remove_proc_entry("rel_time", parent);
remove_proc_entry("anil", NULL);
return -1;
}
link->uid = 0;
link->gid = 100;
return 0;
}

static void __exit proc_win_exit(void) {


remove_proc_entry("rel_time_l", parent);
remove_proc_entry("rel_time", parent);
remove_proc_entry("anil", NULL);
}

128‫صفحه‬
‫;)‪module_init(proc_win_init‬‬
‫;)‪module_exit(proc_win_exit‬‬

‫;)"‪MODULE_LICENSE("GPL‬‬
‫;)">‪MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com‬‬
‫;)"‪MODULE_DESCRIPTION("Kernel window /proc Demonstration Driver‬‬

‫• با استفاده از ‪ Makefile‬معمولی درایور ‪ proc_window.ko‬را بسازید‪.‬‬


‫• با استفاده از ‪ insmod‬درایور را لود کنید‪.‬‬
‫• با استفاده از شکل زیر چندین آزمایش را انجام دهید‪.‬‬
‫• )(‪ proc_win_init‬و در نهایت درایور را از حافظه خارج کنید‪.‬‬

‫بررسی جزئیا ت برنامه ‪:‬‬


‫از سازنده )(‪ proc_win_init‬شروع میکنیم که در آن سبه مبدخل ‪ proc‬را بصبور ت زیبر ایجباد‬
‫کردیم‪:‬‬
‫ل با پدر ‪ (null‬با مجوز دسترسببی ‪ 0755‬و بببا اسببتفاده از )(‬
‫• شاخه ‪ anil‬در زیر شاخه ‪) /proc‬مث ا ً‬
‫‪ proc_mkdir‬آنرا ساختیم‪.‬‬
‫• یبببک فایبببل معمبببولی ‪ rel_time‬در شببباخه فبببوق ببببا مجبببوز ‪ 0666‬و ببببا اسبببتفاده از)(‬

‫صفحه‪129‬‬
‫‪ create_proc_entry‬ساختیم‪.‬‬
‫• یک لینک نرم )‪ (Soft link‬به نام ‪ rel_time_l‬به فایل ‪ rel_time‬در شاخه مشابه و با استفاده‬
‫از )(‪ proc_symlink‬ساختیم‪.‬‬
‫تببابع تخریبگببر )(‪ remove_proc_entry‬مرتبببط بببا چیزهببایی کببه سبباخته بببودیم و تببابع )(‬
‫‪ proc_win_exit‬مد ت زمان درخواست را نمایان میسازد‬
‫برای هر مدخلی که در ‪ /proc‬ایجاد کردیم‪ ،‬یک ساختار مرتبط ‪ proc_dir_entr‬را هم ساختیم‪.‬‬
‫نکته‪ :‬برای هر کدام چند فیلد باید بروزرسانی شود‪:‬‬
‫• ‪ mode‬مجوز دسترسی فایل‬
‫• ‪ uid‬شناسه یکتای کاربر فایل‬
‫• ‪ gid‬شناسه یکتای گروه فایل‬

‫بعلوه برای فایل معمولی دو اشاره گر تابع برای خواندن و نوشتن فایل باید معرفی شود ‪:‬‬

‫)‪int (*read_proc)(char *page, char **start, off_t off, int count, int *eof, void *data‬‬

‫‪int (*write_proc)(struct file *file, const char __user *buffer, unsigned long count,‬‬
‫)‪void *data‬‬

‫)(‪ write_proc‬خیلی شبیه به )(‪ write‬در درایورهای کاراکتری است‪ .‬در پیاده سازی تببابع فببوق ‪ ،‬کبباربر‬
‫میتواند از عدد ‪ 0‬تا ‪ 9‬را در فایل بنویسد و مطابق آن وضعیت داخلی را تنظیم میکند‪.‬‬
‫)(‪ read_proc‬در پیادد‌هسازی فوق ‪ ،‬وضعیت جاری و مد ت زمانی که از بببو ت شببدن سیسببتم میگببذارد را‬
‫بسته به وضعیت جاری در واحدهای مختلف نمایش میدهد ‪.‬لحظه برای وضعیت ‪ ، 0‬میلی ثانیه برای وضعیت‬
‫‪ ، 1‬ثانیه و میلی ثانیه برای وضعیت ‪ ، 2‬ساعت ‪ ،‬دقیقه و ثانیه برای وضببعیت ‪ 3‬و بببرای سببایر مقببادیر چیببزی‬
‫پیادد‌هسازی نشده است‪.‬‬
‫دقت محاسبا ت را حتما ًا چک کنید‪ .‬شکل زیر مقدار زمان روشن بودن سیستم را در خروجی دستور ‪ top‬نشان‬
‫میدهد‪.‬‬

‫صفحه‪130‬‬
‫تمام ساختارها و معرفی توابع مرتبط با ‪ /proc‬در هدر فایل >‪ <linux/proc_fs.h‬تعریف شده است‪.‬‬
‫معرفی توابع و ماکروهای مرتبط با زمان سیستم هم در هدر فایل >‪ <linux/jiffies.h‬تعریببف شببده اسببت‪.‬‬
‫نکتبببه ‪ :‬زمانهبببای واقعبببی بوسبببیله تفریبببق ‪ INITIAL_JIFFIES‬محاسببببه میشبببود‪ .‬در موقبببع ببببو ت‬
‫‪ INITIAL_JIFFIES‬صفرمیشود و از آن به بعد زمان در ‪ INITIAL_JIFFIES‬قببرار میگببرد و در نهببایت‬
‫ساخت شاخه با نام ‪ anil‬فقط یک نام است که شما میتوانید هر نامی که دوست دارید را انتخاب کده و بجای‬
‫آن استفاده کنید‪.‬‬

‫صفحه‪131‬‬
‫لهفدهم‪:‬چگونگی تعامل بین ماژولها‬
‫فص د‌‬
‫در این فصل میخواهیم چگونگی تعامل بین ماژولها )هم درایورهای قابل لود و هم درایورهببای غیببر قابببل‬
‫لود( از قبیل دسترسی به متغیرها‪ ،‬فراخوانی توابع و پاس کردن پارامترها را بررسی خواهیم کرد‪.‬‬

‫توابع و متغییرهای سراسری‬


‫یکی از مبحثی که شاید شما از آن متعجب شوید امکان دسترسی به متغییرها و توابع مبباژول از خببارج از‬
‫آنها میباشد‪ .‬سوال اینجاست که اگر فقط متغییرها را سراسری و با ‪ extern‬در هدرفایل تعریف کنیم و بعببد‬
‫نها دسترسی داریم؟ برای پیادد‌هسببازی برنامد‌ههببای معمببولی دقیقبا ًا بببه‬
‫این هدر فایلها را ‪ include‬کنیم به آ د‌‬
‫همین سادگی است‪ .‬اما در توسعه کرنل اینکه همه چیز را استاتیک تعریف کنیم خلف توصیه هاست‪ .‬بصببور ت‬
‫شفرض نیز مواردی هست که ممکن است به متغیرهای سراسری و غیر استاتیک نیز مورد نیاز باشد‪.‬‬
‫پی د‌‬
‫بعنوان مثال یک درایور در چندین فایل نیاز دارد تا یک تابع از یک فایل دیگبر را فراخبوانی کنبد‪ .‬حبال ببرای‬
‫جلوگیری از تداخل هر فضای نام کرنل در هر حالت باید هر ماژول فضای نام مخصوص به خود را داشته باشد‪.‬‬
‫شفرض هیچ تداخلی پیببش‬
‫مزمان لود شوند‪ .‬بنابراین بصور ت پی د‌‬
‫ل هم نام نمیتوانند ه د‌‬
‫میدانیم که دو ماژول کرن ِ‬
‫شفرض هیچ چیز بصور ت سراسری در کرنببل سباخته نمیشبود‬
‫نمی آید‪ .‬البته به این علت است که بصور ت پی د‌‬
‫حتی اگر ما بخواهیم‪ .‬بنابراین برای چنین حالتی ماکروهای زیر در هدر فایل >‪ <linux/module.h‬تعریف‬
‫شده است‪:‬‬

‫)‪EXPORT_SYMBOL(sym‬‬

‫)‪EXPORT_SYMBOL_GPL(sym‬‬
‫)‪EXPORT_SYMBOL_GPL_FUTURE(sym‬‬

‫شفببرض‬
‫نها در یکببی از پی د‌‬
‫یشود وآ د‌‬
‫در هر کدام ازماکروهای فوق ‪ symbol ،‬بعنوان پارامتر پاس م د‌‬
‫نهبا ببرای‬
‫شهبای _‪ gpl‬یبا _‪ gpl_future‬قبرار میگیرنبد‪ .‬از اینبرو فقبط یکبی از آ د‌‬
‫ها ببه ترتیبب در بخ د‌‬
‫‪ symbol‬خاص مورد نیاز است و ‪ symbol‬میتواند هم نام متغییر و هم نام تابع باشد‪ .‬برای مثال کد زیببر‬
‫یعنی فایل ‪ our_glob_syms.c‬را بررسی میکنیم‪:‬‬
‫>‪#include <linux/module.h‬‬
‫>‪#include <linux/device.h‬‬

‫صفحه‪132‬‬
static struct class *cool_cl;
static struct class *get_cool_cl(void)
{
return cool_cl;
}
EXPORT_SYMBOL(cool_cl);
EXPORT_SYMBOL_GPL(get_cool_cl);

static int __init glob_sym_init(void)


{
if (IS_ERR(cool_cl = class_create(THIS_MODULE, "cool")))
/* Creates /sys/class/cool/ */
{
return PTR_ERR(cool_cl);
}
return 0;
}

static void __exit glob_sym_exit(void)


{
/* Removes /sys/class/cool/ */
class_destroy(cool_cl);
}

module_init(glob_sym_init);
module_exit(glob_sym_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs.com>");
MODULE_DESCRIPTION("Global Symbols exporting Driver");

‫ شده باشد یک ساختار مرتبببط بببا خببود دارد کببه در بخشببهای جببدول‬export ‫ که‬symbol ‫هر‬
__) ‫ کرنببل‬CRC ‫( و جببدول‬kstrtab__) ‫ جببدول رشببته کرنببل‬، (ksymtab__) ‫ کرنببل‬symbol

133‫صفحه‬
‫نها مقدور است قرار دارند‪.‬‬
‫‪ (kcrctab‬که بصور ت سراسری دسترسی به آ د‌‬

‫شکل زیر یک قطعد‌های از ‪ /proc/kallsyms‬را قبل و بعد از لود شدن مبباژول ‪our_glob_syms.ko‬‬
‫نشان میدهد که طبق معمول با یک ‪ Makefile‬کامپایل شده است‪:‬‬

‫کد زیر هد فایل ‪ our_glob_syms.h‬را که باید بوسیله ماژولهببایی کببه ‪ symbol‬هببای ‪ cool_cl‬و‬
‫‪ get_cool_cl‬را که از ‪ export‬استفاده میکند را ‪ include‬نماید‪:‬‬

‫‪#ifndef OUR_GLOB_SYMS_H‬‬
‫‪#define OUR_GLOB_SYMS_H‬‬

‫__‪#ifdef __KERNEL‬‬
‫>‪#include <linux/device.h‬‬

‫صفحه‪134‬‬
‫;‪extern struct class *cool_cl‬‬
‫;)‪extern struct class *get_cool_cl(void‬‬
‫‪#endif‬‬
‫‪#endif‬‬

‫شببکل فببوق همچنیببن فایببل ‪ Module.symvers‬را نشببان میدهببد کببه بوسببیله کامپایببل مبباژول‬
‫‪ our_glob_syms‬ساخته شده است‪ .‬این فایل حاوی جزئیببا ت بیشببتر تمببام ‪ symbol‬هببای ‪export‬‬
‫شده در شاخد‌ه میباشد‪ .‬به جز ‪ include‬شدن هدر فایل فوق ‪ ،‬ماژولهایی که از ‪ sysmbol‬های ‪export‬‬
‫شده استفاده میکنند باید در شاخه ‪ build‬خودش فایل ‪ Module.symvers‬را داشته باشد‪.‬‬
‫نکته ‪ :‬هدر فایل >‪ <linux/device.h‬در مثال فوق با ‪ include‬کردن چندین توصیف و تعریف مرتبط‬
‫نها را مورد بحث قرار دادیم‪.‬‬
‫ل در درایورهای کارکتری آ د‌‬
‫با کلس شروع شده است که قب ا ً‬

‫پارامترهای ماژول‬
‫میدانیم که در یک برنامد‌هی معمولی میتوان برنامه را با پارامترهایی فراخوانی کرد و به آرگومانهببایی کببه‬
‫برنامه با آن فراخوانده شده در داخل برنامه دسترسی داشت ولی آیا یک ماژول نیز اینگونه است؟‬
‫جواب مثبت است‪ .‬پارامترها میتوانند در زمان لود شدن درایورها وقتی که از ‪ insmod‬استفاده میکنیم به آن‬
‫لهای کرنببل حببتی‬
‫پاس شوند و شگفت انگیزاست که بگوییم برخلف آرگومان یک برنامد‌هی معمولی ‪ ،‬در ماژو د‌‬
‫میتوان بوسیله تعامل با ‪ sysfs‬این پارامترها را تغییر داد‪.‬‬
‫پارامترهببای مبباژول بوسببیله مبباکروی زیببر)کببه در >‪ <linux/moduleparam.h‬کببه از طریببق‬
‫یشود‪:‬‬
‫>‪ <linux/module.h‬تعریف گردیده است( پیکربندی م د‌‬

‫)‪module_param(name, type, perm‬‬

‫که ‪ name‬نام پارامتر ‪ type ،‬نوع پارامتر و ‪ perm‬به مجوز ‪ sysfs‬فایل مربببوط بببه ایببن پببارامتر ببباز‬
‫یشببوند ‪byte, short, ushort, int, uint, long, ulong,‬‬
‫میگردد‪ .‬نوع هایی که پشببتیبانی م د‌‬
‫‪ charp (character pointer), bool‬یا ‪ (invbool (inverted Boolean‬هستند‪.‬‬
‫کد ماژول زیر ‪ module_param.c‬یک نمونه از پارامتر ماژول را نمایش میدهد‪:‬‬

‫>‪#include <linux/module.h‬‬
‫>‪#include <linux/kernel.h‬‬

‫صفحه‪135‬‬
static int cfg_value = 3;
module_param(cfg_value, int, 0764);

static int __init mod_par_init(void)


{
printk(KERN_INFO "Loaded with %d\n", cfg_value);
return 0;
}

static void __exit mod_par_exit(void)


{
printk(KERN_INFO "Unloaded cfg value: %d\n", cfg_value);
}

module_init(mod_par_init);
module_exit(mod_par_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <[email protected]>");
MODULE_DESCRIPTION("Module Parameter demonstration Driver");

136‫صفحه‬
‫شکل فوق تجربه من از کار با برنامه پارامتر ماژول را نمایش میدهد‪.‬‬

‫صفحه‪137‬‬
‫شکل فوق تجربه من در کار با پارامتر ماژول را با یوزر ‪ root‬نمایش میدهد‪.‬‬
‫ل باید تعریببف شببود‪.‬‬
‫نکته قابل توجه اینکه قبل از مقداردهی پارامتر ‪ ،‬متغییری با نام مشابه و با نوع سازگار قب ا ً‬
‫سپس برای مشاهده خروجی های نمایش داده شده کارهای زیر را باید انجام دهید‪:‬‬

‫• ساخت درایور )فایل ‪ ( module_param.ko‬با استفاده از ‪Makefile‬‬


‫• لود کردن درایور با استفاده از ‪) insmod‬با و بدون پارامتر(‬
‫• کار با مدخل ‪ /sys‬مرتبط با ماژول‬
‫• و در نهایت خارج کردن ماژول از حافظه‬

‫نکته ها‬
‫شفرض وقتی که پبارامتری را ببه مباژول پباس نکنیبم مقبدار ‪ ۳‬ببه متغییبر‬
‫مقدار دهی )‪ (3‬بصور ت پی د‌‬
‫یشود ‪.‬‬
‫‪ cfg_value‬پاس م د‌‬
‫مجوز ‪ 0764‬به کاربر اجرا کنند مجوز ‪ ، rwx‬به گببروه آن مجببوز ‪ -rw‬و بببه بقیببه مجببوز ‪ --r‬را بببه فایببل‬

‫صفحه‪138‬‬
‫‪ cfg_value‬تحت پارامترهای ‪ module_param‬در زیر شاخه ‪ /sys/module/‬میباشد میدهد ‪.‬‬
‫بررسی کنید‬
‫خروجی دستور ‪ dmesg |tail‬در هببر ‪ insmod‬و ‪ rmmod‬بببرای خروجببی هببای ‪ printk‬ی‬
‫موجود در برنامه را بررسی کنید‪.‬‬
‫سعی کنید در فایببل ‪ /sys/module/module_param/parameters/cfg_value‬بببا یببک‬
‫یوزر معمولی بنویسید )به غیر از یوزر ‪ (root‬و نتیجه را بررسی کنید‪.‬‬

‫موفق باشید‪.‬‬

‫صفحه‪139‬‬

You might also like