بایگانی برچسب: s

زبان ماشین، رمزگشای دستور العمل

در پست های گذشته بسیار بسیار در مورد زبان ماشین و اسمبلی بحث کردیم، اما این بار میخوایم بررسی کنیم که یک دستور، چطور کار میکنه. برای این کار، ما از ساده ترین دستورات استفاده میکنیم. قبل از اون، باید ببینیم که چه اتفاقی میفته که دستورات ما، شناسایی میشن! پس میایم و دستوراتمون رو بررسی میکنیم.

یک دستور در ماشین چطور خونده میشه؟

این خیلی مهمه که بدونیم یک دستور به چه شکلی خونده میشه. بسیار خوب، دستور زیر رو در نظر بگیرید :

کد   
MOV AX, 0

 

این دستور ماشین کد به این شکل داره :

کد   
B8 0000

 

که البته این ماشین کد، یک ماشین کد هگزادسیمال هست. اون چهار صفری که داریم که مقداری هست که خواستیم توی AX قرار بدیم و باهاش کاری نداریم، ولی B8 ما ، همون دستوریه که لازمه رمزگشایی شه و به صورت دو دویی به این شکل در میاد :

کد   
1011 1000

 

خب ما یک «دیکدر» (که یک مدار متشکل از n ورودی و دو به توان n خروجی هست) داریم، که این اعداد رو به ترتیب روشن و خاموش میکنه، و به این شکله که ما به  دستور میرسیم. در واقع مدارات مربوط به دستور رو خواهیم داشت.

بسیار خوب، حالا که میدونیم چه چیزی باعث میشه که ما بتونیم زبان ماشین رو درک کنیم، بیایم بررسی کنیم که :

زبان ماشین از چند بخش تشکیل شده؟

زبان ماشین، اول به صورت اسمبلی نوشته میشه که به زبون انسان خیلی خیلی نزدیکه. یعنی هرکس کمی انگلیسی بدونه میدونه که کلمه ای مثل ADD یعنی جمع کردن. اما، حالا مشکلی که هست اینه که وقتی «اسمبلی» به «آبجکت کد» یا همون کد هگزادسیمال تبدیل میشه رو هرکسی نمیتونه بخونه. پس راه حل چیه؟

اول باید ببینیم که یک خط آبجکت کد از چیا تشکیل میشه! بیاید این مثال رو در نظر بگیریم باز :

کد   
MOV AX, 0 ;B8 0000

 

خب اینجا من آبجکت کد رو به صورت کامنت در آوردم. دو بخش داریم، یکیش چهارتا صفر، که بهش عملوند یا Operand میگیم و دیگری «کد عملیات» یا Operation Code هست. مثلا برای انتقال به AX همیشه از B8 استفاده میشه. چرا؟ چون اینتل اینطور خواسته 😀 . بعد از این، این کد هگزادسیمال به یک کد باینری تبدیل میشه که CPU میتونه اون رو برای ما اجرا کنه.

از کجا آبجکت کد رو بخونیم؟

همه اسمبلر های موجود میتونن فایلی تولید کنن که آبجکت کد، سمبلهای به کار رفته و … رو شامل شه، و در اسمبلر MASM این فایل با پسوند LST ساخته میشه. شما میتونید با باز کردن این فایل با یک ویرایشگر متنی و خوندنش، ببینید که هر دستوری چه کدی رو داره.

یک برنامه نمونه

خب، من یک برنامه اسمبلی خیلی ساده نوشتم که اجرا کردنش هیچ خروجی ای نداره (:D ) ولی خب آبجکت کد ها تولید شده و اسمبل بدون مشکل صورت گرفته. این برنامه با آبجکت کدهاش به صورت کامنت به این شکله (بررسی دستور MOV در ۱۲ حالت مختلف) :

کد   
.MODEL SMALL
.8086
.STACK 
.DATA
.CODE
MOV AX, 0 ;B8 0000
MOV BX, 0 ;BB 0000
MOV CX, 0 ;B9 0000
MOV DX, 0 ;BA 0000
MOV AH, 0 ;B4 00
MOV BH, 0 ;B7 00
MOV CH, 0 ;B5 00
MOV DH, 0 ;B6 00
MOV AL, 0 ;B0 00
MOV BL, 0 ;B3 00
MOV CL, 0 ;B1 00
MOV DL, 0 ;B2 00
END

برای جلوگیری از ابهام هم مقداری که توی رجیسترها ریخته شده صفر هست، به این شکل کد ماشین بسیار ساده تر شده.

تا پست های بعدی، و اسمبلی بیشتر، موفق و موید و آزاد و شاد باشید! 🙂

Share

گسترش Makefile ها – استفاده از متغیر در Makefile

توی این پست بود که تصمیم گرفتم در مورد نوشتن Makefile توضیح بدم. اما قضیه نوشتن میک فایل، به اون چیزی که اونجا مطرح کردیم محدود نمیشه. میتونیم توی Makefile هامون تعدادی متغیر داشته باشیم. متغیر میتونه کامپایلرمون، ورودی ها و خروجی هاش رو مشخص کنه. حتی سوییچ هایی که برای کامپایل کردن برنامه نیاز داریم میتونیم توی Makefile قرار بدیم. بسیار خوب، خود میک یه دستور echo هم داره که میتونیم برای چاپ پیام ازش استفاده کنیم (به جای استفاده از echo بش). حالا سوال پیش میاد که چطور از این دستور ها و قابلیت ها استفاده میکنیم؟

این خط رو در نظر بگیرید :

کد   
gcc -std=c99 main.c mpc.c -ledit -lm -o main -Wincompatible-pointer-types

الان ما میتونیم هرچی که با L شروع میشه رو توی یک متغیر بریزیم، std رو هم همینطور و همچنین incompatible-pointer-types رو. اینطوری چه سودی بهمون میرسه؟ یک میک فایل تر و تمیز خواهیم داشت. پس به این شکل توی میک فایلمون درش میاریم :

کد   
CC = gcc
STD = -std=c99
LINK = -ledit -lm
FLAGS = -Wincompatible-pointer-types

الان هرچی که لازم بود رو توی متغیرهامون گذاشتیم، ولی نوع استفاده از متغیر هم مهمه. همون کد بالا رو در نظر بگیرید، حالا میایم از متغیرها استفاده میکنیم و به چنین چیزی میرسیم:

کد   
$(CC) $(STD) main.c mpc.c $(LINK) -o main $(FLAGS)

دقت کنید که پرانتز هم برای صدا زدن متغیرها لازمه. الان ما یک میک فایل تر و تمیز داریم. کل میک فایلمون به این شکل در اومده الان :

کد   
CC = gcc
STD = -std=c99
LINK = -ledit -lm
FLAGS = -Wincompatible-pointer-types
all:
	$(CC) $(STD) main.c mpc.c $(LINK) -o main $(FLAGS)
 
clean:
	rm main

خب الان میخوایم یک پیام هم چاپ کنیم و به کاربر خوش آمد بگیم، یا بگیم که صبور باشه و بعد کامپایل شدن پیام کامپایل با موفقیت رو چاپ کنه! کافیه به این فرمت چیزی که میخوایم رو بنویسیم:

کد   
@echo "Compiling proccess may take a long time, please be patient"

و به این شکل پیامتون رو هرجا که دوست داشتید قرار بدید. البته Makefile های خیلی خیلی گسترده تر هم وجود دارند که به معنای واقعی میشه گفت یک کمپانی کامپایل هستند.

امیدوارم که این پست هم مفید واقع شده باشه، چون واقعا رفرنس برای نوشتن Makefile توی اینترنت نیست.

موفق و پیروز باشید 🙂

Share

ساخت کراس کامپایلر GCC

اگر لینوکسی باشید (یا حداقل با یکی از سیستم عاملهای خانواده *NIX کار کرده باشید) ، به احتمال خیلی زیاد GCC رو میشناسید. GCC که مخفف GNU Compiler Collection هست یک کامپایلر بزرگ، یا بهتر بگیم یه مجموعه از کامپایلرهاست که توسط پروژه گنو ارائه شده. این کامپایلر به صورت پیشفرض روی تمام توزیع های لینوکس هست (چون چیزیه که عمده ابزارهای لینوکس باهاش کامپایل شدند) و احتمالا روزهای اولی که خواستید توی لینوکس کد C بزنید با این کامپایلر بوده و البته کلی هم فحش دادید بهش 😀 . اگر کمی پیش بریم و برسیم به کسایی که تجربه ساخت LFS رو داشتن، اونها هم احتمالا با کامپایل کردن GCC مواجه شدن. شاید نفس گیر ترین بخش ماجرا در LFS همین GCC باشه ، چون هم طولانیه و هم کلی کانفیگ داره! . اما خب کراس کامپایلر قضیش یکم متفاوته.

من چند وقتیه (خیلی وقته!) درگیر ساخت یک سیستم عاملم و کلی هم برای خودم تحقیق کردم و داکیومنت خوندم و حتی داکیومنت هام رو بازنشر دادم. اما مشکل اینجاست که سیستم عامل قبلیمون کاملا با اسمبلی بود ولی این یکی رو میخوایم با C پیاده سازی کنیم. به نظرتون چی میشه؟ هیچی نمیشه! فقط نیاز داریم تا به جای این که صرفا از NASM استفاده کنیم، جناب GCC که برای خودش غولیه رو هم بازی بدیم. اما اگر سیستممون ۶۴ بیتی باشه چطور؟ اصلا اگر سیستم عامل مقصدمون برای پردازنده ای مثل ARM نوشته شده باشه چی؟ برای این که بتونیم نتیجه و خروجی درست و حسابی بگیریم، نیاز داریم تا کراس کامپایلر داشته باشیم. (برای دونستن اهمیت کراس کامپایلر این لینک رو بخونید).

خب GCC برای کامپایل شدن به Binutils نیاز داره. همچنین قبل از کامپایل Binutils برای معماری مورد نظرمون، باید این کتابخونه ها رو نصب کنیم :

کد   
sudo apt-get install libgmp3-dev libmpfr-dev libmpc-dev texinfo

خب، حالا که این کتابخونه ها رو نصب کردیم میریم که محیط ساخت و ساز (ممکنه درست نباشه ولی باحال ترین معادل ممکن برای Build Environment بود!) رو فراهم کنیم. من میخوام gcc من، برای i686-elf کار کنه. همچنین نمیخوام خیلی پر و بال بدم به ماجرا و نصب رو راحت و با کاربر خودم انجام بدم، پس این کد ها رو وارد ترمینال میکنم تا توی همون پوشه خونگی، gcc مورد نظرمون قرار بگیره!

کد   
export PREFIX="$HOME/opt/cross"
# فراموش نکنید این پوشه رو خودتون باید بسازید :)
export TARGET=i686-elf
export PATH="$PREFIX/bin:$PATH"

خب، حالا باید binutils رو از اینجا و gcc رو هم از اینجا دانلود کنید، و سورس ها رو درون پوشه ای به اسم src قرار بدید (پوشه src داخل پوشه خونگی شما قرار داره!). بسیار خوب! توی پوشه src پوشه ای بسازید به اسم build-binutils و سپس سورس binutils رو از حالت فشرده خارج کنید. حالا وارد پوشه build-binutils بشید (هیچی رو وارد این پوشه نکنید!) و سپس تایپ کنید :

کد   
../binutils-*/configure --prefix="$PREFIX" \
--target=$TARGET \
--with-sysroot --disable-nls --disable-werror

بعد از زدن این دستور، اسکریپت configure براتون Makefile درست میکنه. من از نسخه ۲.۲۵ استفاده کردم و واقعا بی مشکل بود، شما هم بهتره از همین نسخه استفاده کنید. حالا که Makefile برامون تولید شده کافیه به ترتیب make و سپس make install رو تایپ کنیم تا binutils مون نصب شه. حالا نوبتی هم باشه نوبت gcc دوست داشتنیه!

خب پوشه build-gcc هم بسازیم و بریم داخلش! حالا باید از فایل configure دوباره برای ساختن Makefile استفاده کنیم!

کد   
../gcc-*/configure --prefix="$PREFIX" \
--target="$TARGET" \
--disable-nls \
--enable-languages=c,c++ \
--without-headers

بعد از انجام این مرحله، باید یکی یکی دستورات زیر رو بزنیم :

کد   
make all-gcc
make all-target-libgcc
make install-gcc
make install-target-libgcc

تا gcc برامون نصب شه! الان با فراخوانی دستوری مثل i686-elf-gcc ، میتونیم به gcc ساخته شدمون دسترسی پیدا کنیم!

امیدوارم که این مطلب مفید واقع شده باشه، موفق باشید 🙂

Share

بازی با سوییفت

زبان برنامه نویسی سوییفت ، زبانی که اپل ارائه کرده و همچنین به تازگی هم اوپن سورس شده. این زبان یه زبان باحال، قشنگ و خوش ساخته، و البته قشنگی ماجرا اینه که خیلی راحت میشه روی لینوکس یا FreeBSD و … هم اجراش کرد. فرمورک Foundation هم که یکی از فرمورکهای کلیدی اپل بوده، برای این زبان به صورت اوپن سورس ارائه شده، و این یعنی اوپن سورس بیش از پیش بهشت برنامه نویسان شده (با این حساب حتی اگر دیوایس اپل ندارید میتونید این زبان رو یاد بگیرید و تمرین کنید). بسیار خوب، بریم سراغ این که ببینیم این زبان چه شکلیه و چطوریه! اول از همه این که سینتکس کاملا شبیه Objective C داره، و اگر قبلا آبجکتیو سی کد زدید، سوییفت یاد گرفتن براتون بی نهایت آسون میشه. خب، اولین مثال و به نوعی مثال روتین برای آشنا شدن با یک زبان، یا همون «سلام دنیا» (که در مقاله پیشین بهش اشاره شد) ، به این شکله :

کد   
print("Hello, World!")

خب، این روش برای چاپ رشته خیلی مرسومه (استفاده از یه تابع چاپ و یک رشته) ، اما برای این که مفاهیم بهتر منتقل شن، میتونیم رشته رو داخل یک متغیر بریزیم :

کد   
var welcomeMessage: String = "Hello, World"

خب الان که متغیر welcomeMessage رو داریم میریم که چاپش کنیم :

کد   
print(welcomeMessage)

خب میخوایم بریم سراغ چیزایی که توی یه زبان خیلی مرسومن، مثلا حلقه while و این چیزا! خب اگر C و Objective C و این زبانا رو بلد باشید خیلی کارتون راحته چون دقیقا همونه :

کد   
while(true){
 print(welcomeMessage)
 }

خب این هم چاپ بی نهایت همون پیامی که توی متغیر welcomeMessage ریختیم 😀 . برای اطلاعات بیشتر از ساختارهای کنترلی میتونید این لینک رو بخونید.

بسیار خوب، ساختار فانکشن ها هم همونطور شبیه C و Objective C و البته شبیه Go هم هست! این یه تابع بازگشتیه (که البته توی REPL زبان خیلی اذیتم کرد ولی موقع کامپایل خیلی مظلومانه (:D) اجرا شد!

کد   
func factorial(x: UInt) -> UInt {
 if(x == 0) {
   return 1
   } else {
   return x * factorial(x - 1)
   }
 }

خب این تابع یک عدد صحیح بدون علامت رو میگیره و فاکتوریلش رو حساب میکنه و بعد بر میگردونه، همونطور که دیدید اگر با زبان های شبیه C آشنا باشید خواهید دید که تقریبا هیچ فرق خاصی از نظر سینتکس نداره. فقط باید یک مقدار روی دیتاتایپ ها و … دقیق بشید تا بفهمید قضیه چیه وگرنه در کل اصلا زبان سختی نیست. اگر میخواید یادش بگیرید کتابی که خود اپل ارائه کرده رو میتونید دریافت کنید (روی iBooks هم هست!).

امیدوارم که این مطلب مفید واقع شده باشه.

موفق باشید 🙂

Share

نوشتن Makefile برای تسریع فرایند ساخت

احتمال خیلی زیاد، همه ما یک بار توی عمرمون این صحنه رو دیدیم :

کد   
make
make install
make clean

خب ما وقتی یه سورس کدی رو دریافت میکنیم معمولا همراهشم یه Makefile داریم، سازنده اون برنامه، یا همون دوست عزیزی که سورس کد رو برامون نوشته، میتونست بگه کسی که دانلود کرد دیگه خودش بشینه یکی یکی کد ها رو کامپایل کنه و آخرش لینک کنه و این حرفا. ولی خب چون همه توانایی، وقت یا دانش لازم رو ندارن، معمولا یه Makefile نوشته میشه که این کارها درونش صورت بگیره! توی این پست، قراره ما یاد بگیریم که چطور Makefile بنویسیم. اول ببینیم make چیه؟ همونطور که میدونید make به معنای ساختنه. و نرم افزار make هم ساخته شده که بسازه. طبق تعریفی که توی ویکیپدیا و صفحه رسمیش هست، میگن که «نرم افزاریه که فایل های قابل اجرا از یک سری سورس تولید میکنه». البته کارهای دیگری هم میشه باهاش کرد! مثلا همون سورس ها و … رو باهاش منتقل کنیم به جایی که میخوایم و از این قبیل کارا. خب برای نوشتن یک Makefile نیاز به یک ادیتور متنی، نرم افزار make (دقت کنید این نرم افزار جزئی از استاندارد POSIX به حساب میاد و معمولا روی همه یونیکس-لایک ها نصبه)، یک محیط یونیکس لایک (اگر ویندوزی هستید Cygwin یا یک یونیکس لایک مجازی) نیاز داریم. همین؟ درسته همین. حالا با make قراره چی کار کنیم؟ اول بیایم یه Makefile ساده بنویسیم.

سناریوی کلی ما استفاده از نرم افزار echo هست که اونم در یونیکس لایک ها موجوده. ما میخوایم با استفاده از make و echo پیام معروف Hello, World رو نشون بدیم :

کد   
all:
	echo "Hello, World"

خب اینجا دو تا توضیح داریم :

یکی این که all چیه؟ این all یه سوییچ عمومی هست که توی همه Makefile ها موجوده. وقتی که شما صرفا تایپ کنید make هرچی که به این سوییچ گفته شده اجرا میشه، حالا میتونه روی فایل خاصی تغییر اعمال کنه یا نه، مثل ما صرفا یکی از برنامه ها رو فراخوانی کنه. و این که اون فاصله زیاد برای چیه؟ هر وقت نوشتید all (یا هر سوییچ دیگه ای) ، کافیه بیاید خط بعدی و Tab رو بزنید (دقت کنید مطلقا از Space استفاده نکنید چون با هم فرق دارن!) و دستور مورد نظرتون رو وارد کنید!. الان میتونید این تکه کد رو در یک فایل به اسم Makefile ذخیره کنید و بعدش تایپ کنید make و نتیجه رو ببینید! خواهید دید اول دستوری که بهش دادیم و سپس نتیجش رو نشون میده.

بسیار خوب! بیایم یک سناریوی دیگه پیاده کنیم. فرض کنیم شما اومدید :

یک برنامه به زبان C نوشتید، و الان میخواید اون رو کامپایل کنید، در یک دایرکتوری خاص به اسم path (که زیرشاخه دایرکتوری Root هست) قرار بدید، و سپس با یک دستور فایل باینری رو از دایرکتوری سورس حذف کنید. 

قطعا به صورت دستی هم میشه انجام داد، ولی خب منطقی ترش اینه که بیایم و از Makefile استفاده کنیم! فرض میکنیم اسم سورس شما src.c هست و در دایرکتوری ای قرار داره! خب میک فایل ما به این شکل میشه :

کد   
all: src.c
	gcc -o src src.c
 
install: src
	cp -v src /path
 
clean: src
	rm -v src

خب برای این که اینجا، ما سوییچ های all و install و clean رو روی فایلهای مورد نظرمون اعمال کنیم، اسم فایل های مورد نظر رو جلوی سوییچ ها قرار دادیم. توی all میاد از src.c یک فایل باینری به اسم src میسازه! توی install هم اون باینری رو کپی میکنه توی path (البته چون path زیرشاخه دایرکتوری ریشه ماست، باید make رو با sudo اجرا کنید و فکر کنم ترکیب sudo و make و install شدیدا براتون آشناس 😀 )، توی clean هم اومدیم اون فایل باینری رو پاک کردیم! به همین سادگی!

توی این پست سعی کردم تا حد امکان، ساده توضیح بدم چرا که واقعا ساده تر از این نمیشد make رو توضیح داد. البته میشه Makefile های بسیار پیچیده تری هم نوشت که همه می بینیم هستن! مثلا Makefile های FreeBSD هر کدوم غولی به حساب میان. ولی برای پروژه های ساده، خوندن و دونستن همین پست هم کافیه.

امیدوارم که این پست به دردتون خورده باشه، موفق باشید 🙂

Share

نوشتن یک سیستم عامل ساده – قسمت سه و نیم

این قسمت، حقیقتا ارزش این که به قسمت چهارم تبدیل بشه رو نداره. برای همین تصمیم گرفتم اسمش رو بذارم سه و نیم! توی قسمت قبلی prompt نوشتیم که دستورات رو از کاربر میخوند اما هیچ جوابی نمیداد. توی این قسمت قصدم اینه که صرفا یک ارور رو روی صفحه نمایش بدم. به این شکل :

error

بسیار خوب! در قسمتی که چندین db داشتیم برای تعریف دو پیام اول و بعد شکل prompt و بعدش بافر، این رو وارد کنید :

کد   
badcommand db 'Bad command entered', 0x0d, 0x0a, 0

و بعد قبل از اتمام mainloop یعنی قبل از آخرین پرشمون، این کد رو اضافه کنید :

کد   
mov si, badcommand
call print_string

خب بعد از اسمبل کردن، خواهید دید که هر کامندی (مثل فاصله، کرکتر های الکی و …) که وارد کنید این ارور به شما نمایش داده میشه! و دلیلش اینه که هنوز دستوری به سیستم عاملمون اضافه نشده. در قسمت های بعدی دستورات مورد نیازمون رو هم به سیستم عامل کوچولومون اضافه میکنیم!

ضمیمه : ساخت فایل ISO قابل بوت از سیستم عامل

از اونجایی که میدونستم این سوال ممکنه پیش بیاد که چطور باید این فایل رو به ISO تبدیل کنیم و بوتش کنیم، در این بخش توضیح میدم. اولا که img و حتی bin قابل بوت شدن هستن (اگر مجازی سازتون میذاره که floppy controller اضافه کنید) ولی خب اگر دلتون میخواد ISO درست کنید ، اول فایل bin که اسمبلر داده بهتون رو به img تبدیل کنید :

کد   
dd if=my16bitos.bin of=my16bitos.img

بعد یه فولدر به اسم cdiso بسازید و فایل img رو ببرید داخلش و بعد این دستور رو بزنید (برنامه genisoimage رو نصب کنید حتما!):

کد   
mkisofs -o my16bitos.iso -b my16bitos.img -no-emul-boot cdiso/

بعدش میتونید فایل ISO رو بدید به یه مجازی ساز مثل VirtualBox یا QEMU یا VMWare .

موفق باشید!

Share

نوشتن یک سیستم عامل ساده – قسمت سوم

در دو مقاله قبلی سعی کردیم یک سیستم عامل ساده بسازیم که توی مد حقیقی ۱۶ بیت کار میکنه و دو تا رشته هم نشونمون میده. بسیار خوب، اگر با سیستم عامل هایی مثل DOS کار کرده باشید قطعا با محیط متنی آشنایید. اگر هم مثل من لینوکسی باشید احتمالا بخش بزرگی از کارهاتون رو توی محیط ترمینال انجام میدید. حتی در ویندوز هم بعضی وقتا نیاز شدیدی به CMD یا همون Command Prompt پیدا میکنیم. پس نیاز داریم که یک محیطی بسازیم که دستورات رو از کاربر بگیره و بهشون پاسخ بده. ما در این قسمت، میایم و دستورات رو از کاربر میگیریم. در قسمت های بعدی دستورات مورد نظرمون رو هم اضافه میکنیم.

بعد از نوشتن prompt و قابلیت خوندن ورودی از کاربر، در واقع به این شکل میشه کدمون:

کد   
org 0x7c00
bits 16
 
mov ax, 0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
 
mov si, welcome 
call print_string 
 
mov si, about
call print_string
 
mov si, newline
call print_string
 
mainloop:
 mov si, prompt
 call print_string
 
 mov di, buffer
 call get_string
 
 mov si, buffer
 cmp byte [si], 0
 je mainloop
 
 jmp mainloop
 
welcome db  'Welcome to my OS', 0x0d, 0x0a, 0
about db  'Written in 16-bit real mode', 0x0d, 0x0a, 0
buffer times 64 db 0
prompt db '>> ', 0
newline db ' ', 0x0d, 0x0a, 0
 
print_string:
 lodsb
 or al, al
 jz .done 
 
 mov ah, 0x0e
 int 0x10
 
  jmp print_string
 
 
 .done:
   ret
 
get_string:
 xor cl, cl
 .loop:
   mov ah, 0
   int 0x16
 
   cmp al, 0x08
   je .backspace
 
   cmp al, 0x0D
   je .done
 
   cmp cl, 0x3F
   je .loop
 
   mov ah, 0x0e
   int 0x10
 
   stosb
   inc cl
   jmp .loop
 
   .backspace:
    cmp cl, 0
    je .loop
 
    dec di
    mov byte [di], 0
    dec cl
 
    mov ah, 0x0e
    mov al, 0x08
    int 0x10
 
    mov al, ' '
    int 0x10
 
    mov al, 0x08
    int 0x10
 
    jmp .loop
 
    .done:
      mov al,0
      stosb
 
      mov ah, 0x0e
      mov al, 0x0d
      int 0x10
      mov al, 0x0a
      int 0x10
 
      ret
 
times 510-($-$$) db 0
dw 0xaa55

تفاوتش با قبلی چیه؟ درسته یک تابع بلند و بالای get_string اضافه شده، متغیر buffer هم تعریف شده. یک حلقه اصلی هم به اسم mainloop تعریف کردیم. بیایم ببینیم این همه بخش، چه کارایی میکنن؟

بین بخش های اضافه شده، mainloop بخش ساده تریه. پس اول میریم اون رو بررسی کنیم :

کد   
mainloop:
 mov si, prompt
 call print_string
 
 mov di, buffer
 call get_string
 
 mov si, buffer
 cmp byte [si], 0
 je mainloop
 
 jmp mainloop

در بخش اول، یک اسم برای حلقه اصلی گذاشتیم. سپس متغیر prompt رو توی Source Index فرستادیم و تابع print_string رو صدا زدیم. مثل نوشتن اون چیزایی که قبلا نمایش دادیم (پیام خوشامد گویی و این حرفا!). بعدش هم buffer که الان ۶۴ تا صفره رو داخل Destination Index قرار دادیم و تابع get_string رو صدا زدیم. این تابع، در واقع داره یه چیزی رو از ورودی میخونه (چطوری؟ الان بررسیش میکنیم!) . بعد بافر رو به si میفرستیم و بایت به بایت رو با صفر مقایسه میکنیم. اگر همه بایت ها صفر بود(یعنی دستور خالی بود) ، بر میگردیم سر خونه اول. در آخر هم یک پرش میکنیم به حلقه اصلی. چه دستوری وارد شده باشه، چه نشده باشه! حالا بیایم ببینیم تابع get_string مون چطوری کار میکنه؟

کد   
get_string:
 xor cl, cl
 .loop:
   mov ah, 0
   int 0x16
 
   cmp al, 0x08
   je .backspace
 
   cmp al, 0x0D
   je .done
 
   cmp cl, 0x3F
   je .loop
 
   mov ah, 0x0e
   int 0x10
 
   stosb
   inc cl
   jmp .loop
 
   .backspace:
    cmp cl, 0
    je .loop
 
    dec di
    mov byte [di], 0
    dec cl
 
    mov ah, 0x0e
    mov al, 0x08
    int 0x10
 
    mov al, ' '
    int 0x10
 
    mov al, 0x08
    int 0x10
 
    jmp .loop
 
    .done:
      mov al,0
      stosb
 
      mov ah, 0x0e
      mov al, 0x0d
      int 0x10
      mov al, 0x0a
      int 0x10
 
      ret

خب اولین کاری که کردیم انجام یه xor روی cl بوده. در واقع هشت بیت پایینی CX رو با خودش XOR کردیم. این یک کلک مرسوم هست برای تولید عدد صفر، مواقعی که نمیخوایم رجیسترهامون صفر شن. بعدش یک حلقه تعریف کردیم. توی این حلقه، اومدیم چه کردیم؟ عدد صفر رو توی AH قرار دادیم سپس از وقفه ۱۶ هگزادسیمال استفاده کردیم. این وقفه منتظر میمونه تا یک کلید فشرده شه. بعدش عدد هگزادسیمال 08 رو وارد AL کردیم. این چی کار میکنه؟ این چک میکنه کلید backspace فشرده شده یا نه. سپس در صورت برابری، میره به زیرتابع backspace که تعریف کردیم. بعدش هم با قرار دادن 0D در AL چک میشه که آیا Enter فشرده شده یا نه؟ و اگر درست باشه، میره به زیرتابع done که باز هم تعریف شده و مشاهده میکنید. بعد اومدیم محتوای CL رو با 3F مقایسه کردیم ، این دستور چک میکنه آیا ۶۳ کرکتر وارد شده؟ و سپس فقط به Enter و Back Space اجازه میده که کار انجام بدن. در واقع نمیتونید کلید دیگری رو فشار بدید. بعدش هم 0E رو داخل AH گذاشتیم و از همون وقفه معروف بایوس استفاده کردیم. این پروسه به شما اجازه میده هر وقت کلیدی رو فشردید، بدون درنگ کرکتر مورد نظرتون رو روی صفحه مشاهده کنید. حالا نوبتی هم باشه، نوبت زیرتابع هایی هست که داریم. اولیش، backspace هست که به این شکله :

کد   
.backspace:
    cmp cl, 0
    je .loop
 
    dec di
    mov byte [di], 0
    dec cl
 
    mov ah, 0x0e
    mov al, 0x08
    int 0x10
 
    mov al, ' '
    int 0x10
 
    mov al, 0x08
    int 0x10
 
    jmp .loop

این زیرتابع، اول میاد CL رو با صفر مقایسه میکنه. اگر برابر بود میره دوباره توی حلقه اصلی زیرتابع که تعریف کردیم. اگر نبود DI رو یک واحد کم میکنه، بعد کرکتر رو پاک میکنه و همچنین یکی از CL کم میکنه، چرا؟ چون طول رشته ما توی CL ذخیره شده. بعد میایم عدد 0x0e رو وارد AH میکنیم (این تابع مربوط به چاپ کرکتر ها و رشته ها توی وقفه 0x10 بایوس هست) ، بعد 08 رو وارد AL میکنیم که پروسه فشرده شدن backspace رو کنترل میکنه بعد هم وقفه 0x10 رو اجرا میکنیم. بعدش میایم یک کرکتر خالی میریزیم توی AL و بعد باز وقفه اجرا میشه و بعدش هم کرکتر خالی رو پاک میکنیم. در آخر هم به Loop برمیگردیم. حالا بریم ببینیم done اینجا چیکار میکنه؟

کد   
.done:
      mov al,0
      stosb
 
      mov ah, 0x0e
      mov al, 0x0d
      int 0x10
      mov al, 0x0a
      int 0x10
 
      ret

حقیقتا این تابع برای اینه که وقتی یک رشته وارد کردیم و enter رو زدیم عمل کنه حالا ببینیم چطور کار میکنه؟ اول عدد صفر رو میذاریم داخل AL که نقش NULL terminator رو داره. بعدش دستور stosb رو میزنیم که رشته رو ذخیره میکنه. بعدش هم 0E رو وارد AH میکنیم (همون تابع معروف 😀 ) و بعدش 0D رو در AL میذاریم (اتمام رشته) و کارمون رو با وقفه تموم میکنیم. بعدش با قرار دادن 0x0a و اجرای وقفه معروف، میریم خط بعدی. ret هم که تابعمون رو تموم میکنه!

حالا میتونید با خیال راحت با سیستم عامل ۱۶ بیتی خودتون بازی کنید! برای این که ببینید چه اتفاقاتی پشت پرده داره میفته، میتونید ویدئوی من رو ببینید که سیستم عامل رو اسمبل کردم و در ویرچوال باکس اجراش کردم 🙂

توی ویدئو می بینید که با ورود نوشته ها، دستوری اجرا نمیشه. ولی در آینده چند تا دستور ساده برای سیستم عامل سادمون خواهیم نوشت.

موفق باشید 🙂

Share

نوشتن یک سیستم عامل ساده – قسمت اول

همونطور که در جریانید، مدتهای زیادی بود که من دنبال ساخت یک سیستم عامل بودم. خب ساخت سیستم عامل دانش بالایی میخواد. حداقلش اینه که باید بدونیم چی میخوایم! ولی اگر صرفا برای فان بخوایم چنین کاری کنیم، میتونیم یک سیستم عامل خیلی ساده بنویسیم و اگر احتمالا یه کامپیوتر قدیمی و داغون داریم، روی اون نصبش کنیم.

خب، بیاید اول ببینیم چی نیاز داریم؟ اول از همه نیاز داریم که اسمبلی بلد باشیم. احتمالا با گشتن توی اینترنت کلی جزوه و کتاب و آموزش اسمبلی پیدا میکنید، همچنین اگر درس زبان ماشین پاس کرده باشید خیلی راحتتره کارتون، چون ایده کلی رو دارید. و البته مهم تر از اون، اینه که لینوکس و برنامه نویسی هم بدونید. اگر هم لینوکس بلد نیستید سعی کنید یاد بگیرید چون یکی از بهترین محیط های این کار به حساب میاد و اگر توی فروم هایی مثل OSDev بچرخید خواهید دید که اکثر توسعه دهندگان از لینوکس استفاده کردند، حتی اونایی که با سی شارپ یا MASM کار رو انجام داده بودن 😀 . دلیل عمده استفادشون از لینوکس (یا هر چیز شبه یونیکسی) وجود ابزارهای خوب توسعه روی این دسته از سیستم عامل هاست. همچنین خیلی از ایده هایی که شما بهش فکر میکنید رو قبلا روی این سیستم عاملها آزمایش کردند و ساختند که یا آزادند و یا نمونه های آزاد دارند.

خیلی خوب، بذارید اول ببینیم چه چیزهایی باید نصب کنیم؟

  1. یک ادیتور متنی، که معمولا همراه سیستم عاملتون نصب میشه. من خودم از nano و gedit و atom استفاده میکنم، و توصیه میکنم شما هم یکی از این ادیتورهای ساده رو انتخاب کنید تا درگیر پیچیدگی های vim و … نشید.
  2. یک اسمبلر که من اینجا از nasm استفاده میکنم، و nasm توی مخازن اوبونتو و دبیان و … موجوده. اگر هم نبود هم باینری و هم سورسش موجوده.
  3. یک مجازی فوق العاده ساده هم نیاز داریم. در واقع مجازی سازی که برای راه انداختن و بوت کردن یه باینری ساده، اذیتمون نکنه. من از qemu استفاده میکنم. حالا شما میتونید از bochs یا VirtualBox یا هرچیز دیگری استفاده کنید  و انتخاب خودتونه.

خب طبیعتا ترمینال هم نیاز داریم که این وسط بتونیم کارامونو باهاش انجام بدیم 🙂

الان کافیه که با اراده قوی، یک فایل به اسم my16bitos.asm باز کنید و شروع کنید کد زدن. خب باید چی بنویسیم؟ یکی از نکات مهمی که اینجا هست اینه که ما نمیتونیم از روتین ها یا وقفه های سیستم عامل استفاده کنیم و نیاز داریم از بایوس استفاده کنیم! دقیقا چون بایوس MBR رو میخونه و از این داستانا که موقع بوت شدن داریم. پس کد ما چنین چیزی میشه :

کد   
MOV AH, 0x0e
MOV AL, 'A'
INT 0x10
JMP $
times 510-($-$$) db 0
DW 0xaa55

خب این شد کد ما. در واقع ما یک سیستم عامل (یا بهتر بگم بوت سکتور) ۱۶ بیتی نوشتیم. که البته این خیلی سادست و در قسمت بعدی پیچیده ترش خواهیم کرد. این سیستم عامل ۶ خطی (شاید به خاطر پیشرفت علوم کامپیوتر و اسمبلر ها و پردازنده ها، الان این کد شش خطه، و ممکن بوده زمانی یه مهندس کامپیوتر یا صرفا یک علاقمند به علوم رایانه رویاش نوشتن چنین چیزی بوده!)، وقتی بوت شه حرف A رو به ما نمایش میده. حالا با چه اتفاقاتی؟

خط اول که میگه عدد شانزده شانزدهی 0x0e رو در هشت بیت بالایی رجیستر AX قرار بده، یکی از روتین های بایوسه. در واقع بایوس میفهمه که بوت سکتور، این برنامه ست. خط دوم میگه کرکتر A رو در هشت بیت پایینی همون رجیستر قرار بده. در خط سوم ما یک وقفه (توجه کنید INT توی اسمبلی مخفف Interrupt یا «وقفه» است نه Integer .) ایجاد کردیم. وقفه 0x10 در بایوس برای نمایش کرکتر ها به کار میره (البته توی روتین 0x0e). در خط چهارم با استفاده از دستور پرش، برنامه رو تا ابد باز نگه داشتیم، که وقتی سیستم عامل بوت میشه همچنان روشن بمونه. یکی از سخت ترین خط ها از نظر درک، خط پنجمه. خط پنجم چی کار میکنه؟ ۵۱۰ بایت اول دیسک (این سیستم عامل میتونه از روی یک فلاپی یا هارد دیسک یا هر دیوایس دیگری بوت شه) رو با صفر پر میکنه و حالا چرا؟ چون ما میخوایم در دوبایت نهایی، عدد جادویی 0xaa55 رو قرار بدیم که بوت سیستم عامل بهش وابسته است.

بسیار خوب، تا اینجای کار فهمیدیم که این کد چی کار میکنه. حالا با nasm کد رو اسمبل میکنیم تا به یه فایل قابل بوت شدن برسیم :

کد   
nasm -f bin -o my16bitos.bin my16bitos.asm

اکنون در دایرکتوری جاریتون، باید فایل my16bitos.bin رو ببینید. کافیه تا با qemu اجراش کنید به این شکل :

کد   
qemu-system-i386 my16bitos.bin

پس از اجرای دستور و باز شدن پنجره مربوط به QEMU ، و نمایش اطلاعات مربوط به QEMU ، شاهد نمایش حرف A خواهید بود. تبریک میگم! این اولین تلاش شما برای ساخت یک سیستم عامله! در قسمت بعدی این سیستم عامل رو کامل تر میکنیم تا بتونه یه سری اطلاعات رو از کاربر بخونه و جواب بده.

پس تا مطلب بعدی خداحافظ!

Share

استفاده از اسمبلر MASM روی لینوکس

اگر شما هم هم رشته من باشید و زبان ماشین برداشته باشید، به احتمال بسیار بسیار بالا استاد این درس، کدهایی بهتون میده که با MASM اسمبل میشن، و طبیعتا شما ناراحت خواهید شد که چرا این اسمبلر نسخه لینوکسی نداره و … 😀 . و جالبه بدونید راهی که توی این پست به شما ارائه میکنم، نه تنها برای لینوکس و سیستم عاملهای غیر از ویندوز، بلکه روی ویندوز ۶۴ بیتی هم باید اجرا بشه.

اولین چیزی که نیاز دارید، این هست که یک ایمولاتور برای شبیه سازی محیط سیستم عامل قدیمی DOS تهیه کنید. برای ویندوز و لینوکس و BSD ها و … ، نرم افزاری ارائه شده به اسم DOSBox که محیط داس رو شبیه سازی میکنه. توی اوبونتو/دبین با این دستور نصب میشه :

کد   
sudo apt-get install dosbox

سپس، شما نیاز به دانلود اسمبلر MASM دارید که میتونید از اینجا دانلودش کنید.

پس از دانلود MASM و خارج کردن اون از حالت فشرده، حالا نیازمند اجرای داس باکس و سوار کردن پوشه MASM هستید. برای این کار ، داس باکس رو اجرا کنید، و دستورات زیر رو درون شل داس تایپ کنید :

کد   
mount f ~/masm
f:

دستور mount پوشه ای که آدرسش را به عنوان ورودی دریافت کرده را درون یک درایو مجازی به نام F سوار میکند و با دستور بعدی، به آن درایو مجازی میرویم. (توجه کنید که پوشه MASM در پوشه خانگی قرار داده شده است. چنانچه در آدرس دیگری قرار داده اید باید مسیر را عوض کنید)

حالا میتوانید با اجرای MASM.EXE یا ML.EXE ، کدی که به زبان اسمبلی نوشته اید را اسمبل کنید.

موفق باشید 🙂

 

Share

ساخت توزیع لینوکس غیرمستقل بدون نیاز به اسکریپت

اصولا برای نوشتن و ساختن یک توزیع لینوکس، شما نیاز به این دارید که یک اسکریپت یا نرم افزار مخصوص تهیه لایو دیسک رو استفاده کنید. اما این که پشت اون نرم افزار ها چی میگذره؟! این مهمه. خب این نرم افزارها معمولا اسکریپت هستند ( و اگر نباشن هم سورسشون موجوده) ، پس میشه با نگاه به کد، به راحتی پی برد که اون پشت چی میگذره. ولی برای این که این راه رو تست کنید، نگاهی به این مطلب من بندازید. بیاید اول ببینیم ساختار دیسک اوبونتو چیه و چطوره؟!

کد   
(CD ROOT)
|-------+casper
|	|-------filesystem.${FORMAT}	
|	|-------filesystem.manifest
|	|-------filesystem.manifest-desktop
|	|-------vmlinuz
|	|-------initrd.img
|
|-------+boot
|	|--------+grub
|	|	 |--------grub.cfg
|	|
|	|-------memtest86+
|
|--------md5sum.txt

 

توی دیسک اوبونتو، همه چیز به شکل بالا چیده شده! همه برنامه ها و کتابخانه ها (و به طور کلی کل سیستم عامل) داخل پوشه کسپر قرار دارند، و همچنین ملزومات بوت شدن سیستم عامل، توی boot . برای لایو بوت شدن هم از تکنولوژی AuFS استفاده میشه (بعدا در موردش مطلبی خواهم نوشت). اما اگر بخوایم ریز تر شیم، بازهم بهتره به توضیحاتی که سازنده های اوبونتو دادن توجه کنیم :

کد   
/casper/filesystem.${FORMAT}: This is the container of the linux filesystem we are going to copy from our harddisk. It is usually a compressed filesystem like squahsfs.
 
 
 
    /casper/filesystem.manifest: This file is optional. You only need it if you decide to include the Ubuntu installer in the CD. The purpose of this file will be explained later.
 
 
 
    /casper/filesystem.manifest-desktop: This file is optional. You only need it if you decide to include the Ubuntu installer in the CD. The purpose of this file will be explained later.
 
 
 
    /casper/vmlinuz: The linux kernel. This is copied form the linux filesystem.
 
 
 
    /casper/initrd.img: the initramfs that contain the customizations necessary for the live CD/DVD.
 
 
 
    /boot/grub/grub.cfg: File containing boot options for the live CD/DVD.
 
 
 
    /boot/memtest86+: Optional file used to test the RAM of the machine form the live CD/DVD.
 
 
 
    /md5sum.txt: Optional file containing checksums for all the files in the CD.

پس اسکریپت هایی که برای «ریمسترینگ» استفاده میشن، این کارها رو میکنند و چنین برنچی رو برای ما میسازند. خب برای ساخت چنین چیزی باید چه کار کنیم؟ قبل تر در مورد ساخت توزیع مطلب نسبتا زیادی نوشتم، چه تو وبلاگ قبلی که روی بلاگفا بود و چه توی این وبلاگ. اما اگر واقعا میخواید کار مشابه اسکریپتی مثل ReLinux انجام بدید، به این لینک سر بزنید.

این روش روی چه توزیع هایی کار میده؟!

این هم سوال بسیار خوبیه، میشه گفت هر توزیعی که ساختار مشابه اوبونتو داشته باشه. حتی اگر شما بخواید توزیعی بر مبنای آرچ لینوکس ارائه بدید، میتونید ازش استفاده کنید، ولی احتمالا زحمت زیادی خواهد داشت براتون. اگر توزیع مبدا ساختار مشابه نداشته باشه، احتمالا نتونید چنین توزیعی ازش بیرون بکشید ولی اگر تونستید هم به احتمال بسیار قوی توزیع جدید ساختار تازه ‌ای ارائه خواهد کرد. به زودی هم لینکی که دادم رو ترجمه میکنم 🙂

موفق باشید!

 

Share