آغاز به یادگیری هوش مصنوعی

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

  • الگوریتم:
    این که بتونید برای یک برنامه، یک الگوریتم بهینه پیدا کنید یا پیشنهاد بدید، مهم ترین عامل در یادگیری و انجام پروژه های هوش مصنوعی به حساب میاد. اگرچه این مورد، همه جا کاربرد داره و کلا برای برنامه نویس و دولپر خوب شدن لازمش داریم؛ ولی اینجا لازمه که به الگوریتم و الفبای موضوع مسلط باشیم. پس، باید یاد بگیریم که چطور با استفاده از الگوریتم ها یک برنامه بسازیم. خب برای یادگیری الگوریتم (اگر بلد نیستید) پیشنهاد من کتاب CLRS هست. هم ترجمه این کتاب در بازار موجوده و هم زبان اصلیش در اینترنت هست.
  • برنامه نویسی :
    برای این که بتونید پروژه هوش مصنوعی انجام بدید باید برنامه نویسی بلد باشید؛ بهرحال بخشی از کامپیوتره و نمیشه ازش در رفت. گرچه ممکنه شما صرفا ایده پردازی یک پروژه هوش مصنوعی رو انجام داده باشید ولی موضوع مهم اینه که شما بتونید همون ایده رو هم چندین بار تست کنید و بعد ارائه‌ش کنید به یک تیم. پس، برنامه نویسی بلد بودن هم از شرایط یادگیری و انجام پروژه در هوش مصنوعی هست. پیشنهاد من هم برای یادگیری زبان، پایتونه که الان تبدیل شده به ابزار شماره یک پروژه های هوش مصنوعی.
  • علوم شناختی :
    علوم شناختی یا Cognitive Science ترکیبیه از روانشناسی، فلسفه ذهن، زیست شناسی مغز و علوم کامپیوتر. البته انقدر ها هم خلاصه نیست و من دارم انقدر خلاصه میگم. برای این که بتونیم پروژه های هوش مصنوعی بزنیم؛ لازم داریم که بلد باشیمش. گرچه طوریه که در حین یادگیری هوش مصنوعی هم، این موضوع رو یاد میگیریم. خیلی از ایده ها و … که در هوش مصنوعی (به ویژه شبکه های عصبی مصنوعی) مطرح شده، حاصل کار دانشمندان شناختی بوده.
شبکه عصبی مصنوعی
شبکه عصبی مصنوعی

 

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

پروژه گاه‌شمار

خب، من در گروه های تلگرامی این پیام رو میذاشتم :

دوستی مدتی پیش (که خیلی هم ازش نگذشته ) یک چالش رو بعنوان تمرین برنامه نویسی پیشنهاد داد، و اون هم نوشتن یک API بود برای دریافت ساعت و تاریخ.
من هم وارد این چالش شدم و یک API با روبی و سیناترا نوشتم، روی گیتهاب هم قرارش دادم ولی الان دپلویش کردم که اگر شما هم خواستید بتونید ازش استفاده کنید :
https://gahshomar-api.herokuapp.com/

خب، این هم پروژه ای بود که محسن بعنوان تمرین خواست که ما یه API بنویسیم، من هم این رو با Sinatra نوشتم و الان دپلوی شده و در دسترسه! امیدوارم ازش لذت ببرید 🙂

Share

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

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

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

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

کد   
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

دیدار با 8086 + کد روشن و خاموش کردن LED

نرم افزارهای زیادی برای کار با مدارات، تراشه ها و سایر دیوایس های الکترونیکی ساخته شده، ولی دو روزی هست که با Proteus دارم کار میکنم و میتونم بگم در این زمینه، از بهترین هاست. یکی از خوبی هاش داشتن تراشه های متعدد، با قابلیت اجرای برنامست. یکی از معروف ترین ریزپردازنده هایی که در این نرم افزار به کار رفته، پردازنده 8086 از اینتل هست. من این رو با این پردازنده ساختم (البته اصلا و ابدا مطمئن نیستم که مدارم درست باشه! ولی خود برنامه ایرادی ازش نگرفت) :

8086led

 

خب این مدار قراره چه کنه؟! قراره به کمک 8086 و یک مدار مجتمع 8255 ، چند تا LED رو به ترتیب خاموش و روشن کنه. یکی از ویژگی های پرتئوس اینه که تراشه هایی که داخلش هستند، همه برنامه پذیرند. البته شما باید برنامه هاتون رو بنویسید بعد اسمبل (یا کامپایل، بسته به نوع برنامه) کنید و بعد فایل اجراییتون رو به خورد تراشه بدید. بسیار خوب، برنامه ای که برای روشن و خاموش کردن LED نوشتم اینه :

کد   
.MODEL SMALL
.8086
.STACK
.CODE
MOV AL, 80
MOV DX, 0FF36h
OUT DX, AL
BEGIN: MOV AL,00
MOV DX, 0FF30h
OUT DX, AL 
CALL DELAY
MOV AL, 0FFh
OUT DX, AL 
CALL DELAY 
JMP BEGIN 
DELAY: MOV CX, 0FFFFh
P0 : DEC CX 
JNE P0 
RET
.DATA
END

خب، این برنامه رو باید با masm32 اسمبل کنید، که مراحلش به این شکله (مراحل اسمبل و لینک کردن در سیستم عامل ویندوز اینطوره، برای لینوکس هم سعی میکنم راه حلش رو پیدا کنم) :

کد   
ml /c /Zi /Zd LED.asm
link16 /CODEVIEW LED.obj,LED.exe,,, nul.def

دقت کنید که کد ها باید پشت سر هم وارد شن، همچنین ، بعد از این عملیات، چنانچه OpCode ها رو بخواید بخونید، میتونید از فایل LST تولید شده توسط اسمبلرتون استفاده کنید، که من اینجا OpCode رو میذارم (حواستون باشه این فقط برای کد سگمنت هست) :

OpCode

این هم از آپکد های این برنامه.

حواستون باشه مداری که کشیده شده ممکنه درست نباشه، در صورتی که برنامه درست اجرا میشه (برنامه تست شده و از این بابت مطمئنم).

امیدوارم شما هم با 8086 لحظات شادی رو تجربه کنید، درسته که یه پردازشگر قدیمیه، ولی کار باهاش شدیدا فانه!

موفق باشید 🙂

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

قطعه کد «سلام دنیا» در زبان های مورد علاقه من – سری دوم

توی این مطلب یه تعداد از زبان های مورد علاقم رو معرفی کردم و درونشون قطعه کد سلام دنیا یا همون Hello World رو نوشتم. الان قصد دارم یه تعداد زبان دیگه رو معرفی کنم و این کد رو درون اون زبانها اجرا کنم. این زبانها هم جنبه فان دارن و در عین حال خیلی هم کاربردین.

۱. Arendelle :

این زبان، که سایتش از این آدرس  در دسترسه، ساخته دست دوست خوبم پویا کاری هست (اطلاعات شخصی ایشون توی وبسایت لینک شده). یک زبان برنامه نویسی فان برای کودکان که کلاینت آی او اس و آندروید هم داره.

کد   
'Hello, World'

۲. Apple Swift

زبان سوییفت اپل (نه اون سوییفتی که برای برنامه نویسی موازی و همروند استفاده میشه) به تازگی اوپن سورس شده و حقیقتا نتونستم تحمل کنم و نصبش کردم اون هم روی اوبونتو! و خب کار باهاش خیلی فانه (و به زودی هم یه سری مطلب در موردش میذارم) و البته اگر فرمورک های درست و حسابی تحت لینوکس هم براش ارائه شه، میشه گفت میتونه به یکی از بی رقیب ترین ها تبدیل شه!

کد   
print("Hello, World")

۳. Ada

خب، زبان Ada هم از زبانهایی بود که همیشه میخواستم یاد بگیرم، مدتها پیش در موردش خوندم و جالب ترین نکته این بود که اسم گذاریش از روی اولین برنامه نویس جهان بوده، پس مصمم شدم که حتما یکم باهاش بازی کنم 🙂

کد   
with Ada.Text_IO; use Ada.Text_IO:
procedure Hello is
 begin
  Put_Line("Hello, World");
 end Hello;

۴. Verilog HDL

این زبان هم برای شبیه سازی سخت افزار ازش استفاده میشه. شدیدا کاربردی و باحاله و میتونید کلی پروژه کول پیدا کنید که روی این زبان هستن، مثلا پیاده سازی اوپن سورس از پردازنده های مختلف مثل MIPS, x86, sparc و … .

کد   
module main;
 initial
  begin
   $display("Hello, World");
   $finish;
  end
endmodule

 

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

موفق باشید.

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

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

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

کد   
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
 
 mov si, buffer
 mov di, cmd_reboot
 call strcmp
 jc .reboot
 
 mov si, buffer
 mov di, cmd_about
 call strcmp
 jc .about
 
 mov si, buffer
 mov di, cmd_help
 call strcmp
 jc .help
 
 mov si, badcommand
 call print_string
 jmp mainloop
 
 .reboot:
  int 0x19
  jmp mainloop
 
 .about:
 mov si, msg_about
 call print_string
 jmp mainloop
 
 .help:
 mov si, msg_help
 call print_string
 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
badcommand db 'Bad command entered', 0x0d, 0x0a, 0
cmd_reboot db 'reboot',0
cmd_about db 'about', 0
msg_about db 'This is a 16 bit operating system, works in real mode and written in assembly language', 0x0d, 0x0a, 0
cmd_help db 'help', 0
msg_help db 'Commands are : about, help and reboot', 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
 
strcmp:
 .loop:
  mov al, [si]
  mov bl, [di]
  cmp al, bl
  jne .notequal
 
  cmp al, 0
  je .done
 
  inc di
  inc si
 
  jmp .loop
 
  .notequal:
   clc
   ret
  .done:
    stc
    ret
 
times 510-($-$$) db 0
dw 0xaa55

 

همونطور که می بینید چن قسمت جدید شروع شده ، مثلا تابع strcmp رو ساختیم، چن تا دستور و پیام تعریف کردیم و زیر تابع های reboot و about و help رو هم تعریف کردیم. خب اول یه توضیح کوچیک : دستورات باید حتما معرفی بشن. چرا؟ چون تابع strcmp ورودی کاربر رو با یه سری اطلاعات از پیش تعیین شده میاد مقایسه میکنه و در صورت برابری با مقدار وارد شده، اون دستوری که در برنامه تعریف شده رو اجرا میکنه. پس بخشی که چندین db پشت هم تعریف شده رو جدی بگیرید! بسیار خوب، یکی از توابع خوب و زیبامون تابع strcmp هست. این تابع چیه؟ ما یه دستوری در اسمبلی داریم به اسم cmp که کارش مقایسه است. حالا برای این که ببینیم دو تا رشته با هم برابرن اومدیم strcmp نوشتیم. در واقع مخفف string compare هست. خب این تابع در کد ما به این شکله :

کد   
strcmp:
 .loop:
  mov al, [si]
  mov bl, [di]
  cmp al, bl
  jne .notequal
 
  cmp al, 0
  je .done
 
  inc di
  inc si
 
  jmp .loop
 
  .notequal:
   clc
   ret
  .done:
    stc
    ret

این تابع ، میاد اول یک بایت از si رو میریزه توی ۸ بیت پایینی AX . بعدش یک بایت از di رو میریزه توی هشت بیت پایینی BX . بعد با cmp مقایسه‌ش میکنه، در صورتی که برابر نبودن، میره سراغ notequal که توضیح داده میشه، و اگر برابر باشن، هشت بیت پایین AX چک میشه و اگر خالی بود میره done. در غیر این صورت یک کرکتر میره جلو (با دستور inc یک خونه به جلو حرکت میکنه). بعد میرسیم به بخش دوست داشتنی notequal ، توی این بخش اول Carry Flag که یک بیت از رجیستر Flag در پردازنده ماست، خالی میشه و تابع تموم میشه. در قسمت done هم این بیت پر میشه و تابع تموم میشه! دیدید؟ بسیار ساده تر از چیزی بود که فکرش رو میکردید! حالا بریم کارکرد دستورات رو بررسی کنیم :

کد   
mov si, buffer
 mov di, cmd_about
 call strcmp
 jc .about

خب این بخش برای همه دستورات یکیه، چی کار میکنه؟ خط اول buffer (رشته ای که کاربر وارد کرده) و خط دوم دستور (دستوری که توی اون بخش توسط db تعریف کردیم) وارد رجیستر های SI و BI میشن. بعد تابع strcmp میاد این دو رشته رو با هم مقایسه میکنه. اگر شرط درست باشه، به زیرتابع مورد نظر میریم. در غیر این صورت، زیرتابع badcommand که در قسمت سه و نیم معرفی شد، فراخوانی میشه. حالا بیایم ساختار about رو بررسی کنیم (توجه کنید about ساختارش با help یکیه پس یکیشون کافیه که بررسی شه) :

کد   
.about:
 mov si, msg_about
 call print_string
 jmp mainloop

خب تا حدود زیادی مثل badcommand عمل میکنه (تقریبا همونه با این تفاوت که یه شرط لازم داره برای اجرا شدن) ، اول رشته مورد نظرمون میره توی SI ، بعد print_string صدا زده میشه، و بعد میریم به حلقه اصلی و منتظر میمونیم تا کاربر دستور بعدی رو بده.

این زیرتابع ها، مشابهن. فقط تابع reboot یکم ممکنه اذیتتون کنه، ولی خب اون رو هم اینجا توضیح میدیم :

کد   
.reboot:
  int 0x19
  jmp mainloop

خب طبیعیه که jmp کارش پرش به حلقه اصلیه و برای این میذاریمش که اگر reboot رو کاربر نزنه ، چی بشه؟ هیچ اتفاقی نیفته و سیستم عامل کوچولومون همچنان منتظر باشه کاربر بهش دستور بده. اما وقفه ۱۹ هگزادسیمال یا 0x19 (که بعضی جاها ممکنه 19h هم نوشته شه) ، چیه؟ این وقفه یکی از وقفه های بایوسه که کارش ریبوت کردن (راه اندازی مجدد) سیستم عاملمونه.

تبریکات فراوان! شما الان یک سیستم عامل زیبا و کوچولو نوشتید، که سه تا دستور داره و اگر دستوری غیر از اینا بهش بدید، بهتون ارور میده. در واقع شما کاری رو پیش بردید که توسعه دهنده ها سالیان پیش انجام میدادن تا کامپیوترها بیشتر و بیشتر کاربرپسند بشن! و از این جهت شدیدا باید خوشحال باشید، چرا؟ چون کارهایی رو دوباره کردید که احتمال خیلی زیاد توی شرکتهای بزرگ کامپیوتری انجام میشده! و همچنین تشکرات فراوان بابت این که مطالب من رو خوندید و براش وقت گذاشتید. بزودی سری جدیدی از این مقالات شروع میشه که سیستم عامل کوچولومون رو میبریم توی مود حفاظت شده ( Protected Mode ) که یک مود ۳۲ بیتی هست و احتمال خیلی زیاد از بوت لودر GRUB برای بوت کردنش استفاده خواهیم کرد!

موفق باشید 🙂

Share

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

در مقاله قبلی، در مورد نمایش یک حرف روی مانیتور صحبت شد. اما یک حرف کافی نیست. حتی نمیشه به صورت حرف به حرف کلمات و جملات رو نشون داد چون واقعا کار سختیه. ما میخوایم یک پیغام (مثل Hello World) رو موقع بوت شدن سیستم عاملمون شاهد باشیم. پس باید چه کنیم؟ یک راه حل اینه که از یک ویژگی ای در زبان مورد نظر (اینجا اسمبلی) استفاده کنیم که بتونه یک رشته رو چاپ کنه. طبیعتا اسمبلی به طور پیشفرض دستوری مثل cout نداره و ما مجبوریم که خودمون اون رو به cout بیاریم. در این قسمت از مقالات «نوشتن یک سیستم عامل ساده» به این میپردازیم که چطور سیستم عامل ۱۶ بیتی ما، یک یا چند جمله رو نشونمون بده! در این قسمت علاوه بر رجیستر AX از سایر رجیستر ها هم استفاده خواهیم کرد. ممکنه بپرسید چرا؟ جواب سوالتون رو توی متن میگیرید!

خب فایل my16bitos.asm رو باز کنید. کل محتواش رو پاک کنید و این رو جایگزینش کنید :

کد   
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
 
welcome db  'Welcome to my OS', 0x0d, 0x0a, 0
about db  'Written in 16-bit real mode', 0x0d, 0x0a, 0
 
print_string:
 lodsb
 or al, al
 jz .done 
 
 mov ah, 0x0e
 int 0x10
 
  jmp print_string
 
 
 .done:
   ret
 
times 510-($-$$) db 0
dw 0xaa55

دیدید ؟ کد خیلی طولانی تر شده نسبت به قبل. طبیعیه چون که ما یه تابع تعریف کردیم و از یه سری چیزای جدید هم استفاده کردیم. خب آماده اید؟ میریم که بخش به بخش سیستم عامل کوچولوی خودمون رو بررسی کنیم!

کد   
org 0x7c00
bits 16

این قسمت، برای اینه که اولا ، اسمبلر آدرس ها رو بفهمه، ثانیا بفهمه که کد چند بیتیه. در آینده کد ما ۳۲ بیت خواهد شد و این بخش هم تغییر خواهد کرد. ولی فعلا تا آموزشهای بعدی همین برنامه رو داریم.

کد   
mov ax, 0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00

این قسمت از کدمون هم مقدار صفر رو توی رجیستر AX میذاره و سپس اون رو به Data Segment, Extra Segment و Stack Segment منتقل میکنه (توجه کنید مستقیما نمیتونیم مقداری رو درون این رجیستر ها بذاریم. پس اول مقدار رو میذاریم توی AX سپس با MOV یکی یکی منتقل میکنیم). در آخر هم مقدار 7C00 هگزا دسیمال رو توی Stack Pointer قرار میدیم (استک از این آدرس شروع میکنه و میاد پایین).  این بخش داره مقدمات اجرای سیستم عامل ما رو فراهم میکنه.

بعد از اونا میرسیم به این بخش :

کد   
mov si, welcome 
call print_string 
 
mov si, about
call print_string
 
welcome db  'Welcome to my OS', 0x0d, 0x0a, 0
about db  'Written in 16-bit real mode', 0x0d, 0x0a, 0

این قسمت تقریبا ساده ترین قسمت برنامست. اومدیم welcome و about که دو رشته هستند رو ریختیم توی Source Index و سپس تابع print_string که کارش چاپ رشته هست هم صدا زدیم. با دستور db هم گفتیم که چه رشته هایی رو مد نظر داریم که چاپ شن. اون اعداد هگزاسیمال بعد از متغیر ها هم معادل New Line هستند (مثلا مقدار n در دستورات بک اسلش در C ).

بعد از این جنگولک بازیا ( 😀 ) میرسیم به کار اصلیمون یعنی نوشتن تابع print_string . این تابع نسبتا سادس ولی خب نیاز به توضیح داره چون دستوراتی رو استفاده کردیم که توی مطلب قبلی نبودن :

کد   
print_string:
 lodsb
 or al, al
 jz .done 
 
 mov ah, 0x0e
 int 0x10
 
  jmp print_string
 
 
 .done:
   ret

خب اول اومدیم اسم تابع رو گذاشتیم print_string (شما میتونید هرچیزی دلتون میخواد بذارید، مثلا دوستی ممکنه بذاره cout که براش ملموس تره و …). بیایم ببینیم خط به خط تابع چه میکنه؟

اون lodsb یک بایت رو از SI میخونه ، بعد رجیستر AL با خودش از نظر منطقی OR میشه. در نهایت اگر نتیجه صفر شد میره به done که done هم همونطور که می بینید داره return میکنه و از تابع خارج میشه. اگر جواب صفر نشه، مقدار 0e هگزا دسیمال وارد AH میشه و سپس وقفه 10 هگزادسیمال که یکی از وقفه های بایوس هست اجرا میشه. در واقع 0e یکی از تابع های وقفه مورد نظر ماست. بعد از اون پرش رخ میده به print_string. در واقع ما یک حلقه نوشتیم که به تعداد کرکتر های رشتمون، تکرار میشه و رشته رو تکرار میکنه.

در نهایت هم باید به اسمبلر بفهمونیم که برناممون یک Boot Sector عه پس طبیعتا از این قطعه کد استفاده میکنیم که در مطلب قبلی توضیحش دادم :

کد   
times 510-($-$$) db 0
dw 0xaa55

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

کد   
nasm -f bin -o my16bitos.bin my16bitos.asm
dd if=my16bitos.bin of=my16bitos.img

بعد یک ماشین مجازی توی ویرچوال باکس ایجاد کنید، یک Floppy Controller بسازید و اولویت بوت رو بهش بدید. سپس img رو بدید به فلاپی کنترلر و سپس ماشین رو روشن کنید. باید چنین صحنه ای رو مشاهده کنید :

 

os

تبریک! شما تقریبا نصف بیشتر راه رو رفتید! در ادامه هم میرسیم به باقی ماجرا و نوشتن prompt و این چیزا!

موفق باشید 🙂

Share

پیاده سازی لیست پیوندی در روبی

لیست پیوندی، یکی از ساختمان داده هایی هست که معمولا توی درس ساختمان داده درس داده میشه (البته در مورد کاربردش در زندگی واقعی چیزی نمیدونم، ممنون میشم بهم بگید) و خب معمولا سر کلاس، توی زبانهایی مثل C یا ++C درس میدنش. اما من داشتم توی نت میگشتم و به این مقاله رسیدم. که این مقاله، توضیح داده چطور میشه توی روبی این ساختمان داده رو پیاده کرد. خب به صورت ساده میریم سراغ پیاده سازی و کم کم پیچیدش میکنیم.

کد   
class Node
 attr_accessor :node, :next
 
 def initialize(node)
  @node = node
 end
end

خب تا اینجا، عملکرد ساده لیست پیوندی رو داریم. همون node و next که next معمولا از جنس اشاره گره. خب ما قاعدتا یک متد دیگری هم نیاز داریم. متدی که نیاز داریم، متدیه که بهمون بگه چیا توی لیستمون ذخیره کردیم. اصولا یکی از مهم ترین متد هاییه که میتونیم توی این کلاس، اضافه کنیم. متد رو به این شکل مینویسیم :

کد   
def self.node_list(node, msg = nil)
    msg ||= ""
    return msg[0..-4] if node.nil?
    node_list(node.next, msg << "#{node.node} -> ")
  end

بسیار خوب! حالا یک متدی مینویسیم که این لیست رو برای ما، برعکس کنه. گرچه چنین متدی نیاز نیست، اما چون توی روبی از این متد برای هش ها و لیست ها (آرایه ها) استفاده شده، بهتره ما هم به لینک لیستمون اضافش کنیم. خب یک متد هم به اسم Reverse ایجاد میکنیم به این شکل:

کد   
def self.reverse(node)
    return node if node.next.nil?
 
    head, swap, node.next = node.next, node, nil
    link = head.next
 
    while link != nil
      head.next = swap
      swap = head
      head = link
      link = link.next
    end
 
    head.next = swap
    head
  end

حالا میتونیم با استفاده از این کلاس، از لیست های پیوندی استفاده کنیم. البته دقت کنید که ما در اینجا در مورد حذف و اضافه کردن Node ها حرفی نزدیم. بلکه صرفا نمایش و معکوس کردن لیست رو بررسی کردیم. امیدوارم کد به کمکتون اومده باشه :).

Share