دستورکار آزمایشگاه سیستم‌عامل


دانشگاه اصفهان

دانشکده مهندسی کامپیوتر

 

 

 

دستورکار آزمایشگاه سیستم‌عامل

 

دکتر علی بهلولی، دکتر مرجان کائدی

 

نسخه 1.2

 

 

پاییز 1401

 


تاریخچه بازبینی‌ها

شماره نسخه

تاریخ

شرح تغییرات

1.0

07/11/1396

نسخه اولیه

1.1

05/12/1400

اصلاحات کلی

1.2

01/09/1401

اصلاحات جزئی

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

پیشگفتار

هدف از این آزمایشگاه، آَشنایی عملی دانشجویان با مفاهیم سیستم‌عامل است. دستورکار این آزمایشگاه در قالب 10 آزمایش تدوین شده است که برمبنای سیستم عامل لینوکس است. به منظور جلوگیری از آسیب‌های احتمالی به سیستم عامل، از ماشین مجازی برای انجام آزمایش‌ها استفاده شده است. در این صورت دانشجو بدون نگرانی از آسیب دیدن سیستم‌عامل به انجام آزمایش‌ها خواهد پرداخت.

مقررات این آزمایشگاه نیز مطابق مقررات عمومی آزمایشگاه‌های دانشگاه است. جلسات آزمایشگاه بلافاصله بعد از حذف و اضافه شروع شده و به صورت هفتگی تا شروع امتحانات پایان ترم ادامه می‌یابد. دانشجویی که سه جلسه غیبت داشته باشد، مردود است.

روند کلی آزمایشگاه به این صورت است که هر یک از دانشجویان باید قبل از هر آزمایش، پیش‌آگاهی آن را از قبل مطالعه کرده، در ابتدای جلسه، در یک امتحان کوچک 15-10 دقیقه‌ای در مورد آن پیش‌آگاهی تسلط خود را نشان دهد ( این کار به خاطر هدایت دانشجویان به مطالعة قبلی مطالب و تقلیل احتمال تقلب در پیش گزارش‌ها در نظر گرفته شده است). پس از امتحان کوچک، دانشجویان هر آزمایش (که معمولا 2 یا 3 نفر هستند)، گام‌های مشخص شده در دستور کار را دنبال کرده، نتایج را در کاربرگ‌های آن آزمایش (که توسط مربی در محل آزمایشگاه در اختیار دانشجو گذاشته می‌شود) ثبت می‌کنند. به این ترتیب کاربرگ‌ها به صورت یک سری برای هر گروه کامل می‌گردد.

در ارزیابی نهایی هر دانشجو، علاوه بر نمرة امتحانات کوچک هر جلسه و نمرة گزارش جلسات (که برای تمام اعضای هر گروه یکسان است)، نظر مربی در مورد آن دانشجو و نتیجة امتحان پایان ترم دانشجو نیز دخالت دارد.

در انتها از تمام همکاران و دانشجویان درخواست می‌‌کنم با طرح پیشنهادهای مشخص و مدون خود برای رفع معایب، ارتقای کیفی و پویایی به عنوان یک ویژگی ضروریِ آزمایشگاه سیستم‌عامل، ادامه دهندة راهی باشند که اولین گام آن برداشته شده است. بر خود لازم می‌دانم از زحمات مهندس مریم طائبی، مهندس فاطمه شفیع زادگان و مهندس مهدی سمیع در بهبود کیفیت این دستورکار تشکر کنم.

دکتر علی بهلولی، دکتر مرجان کائدی

پاییز 1401

Emails: bohlooli@eng.ui. ac.ir, kaedi@eng.ui.ac.ir


 

فهرست مطالب

آزمایش اول: آشنایی با ماشین مجازی و نصب لینوکس... 7

1-1 پیش‌آگاهی. 7

1-1-1 ماشین مجازی. 7

1-1-2 معرفی سیستم‌عامل Linux و نگاهی به ویژگی‌های آن. 7

تاریخچه سیستم‌عامل Unix. 8

تاریخچة سیستم‌عامل Linux. 8

توزیع‌های مختلف لینوکس.. 9

راهنمای سیستم. 9

ویرایشگرها 10

1-2 دستورکار 10

1-3 مراحل نصب ‌‌سیستم‌عامل Ubuntu در VMware. 11

1-4 سوال. 15

آزمایش دوم: سیستم فایل: مدیریت دایرکتوری.. 16

2-1 پیش‌آگاهی. 16

2-1-1 سیستم فایل (File System) 16

2-2 دستورکار 18

آزمایش سوم: سیستم فایل: مدیریت فایل‌ها 19

3-1 پیش‌آگاهی. 19

3-1-1 انواع فایل‌ها در لینوکس.. 19

3-1-2 ویرایشگر vi 21

3-2 دستورکار 22

آزمایش چهارم: سیستم فایل (مجوزهای دسترسی) 24

4-1 پیش‌آگاهی. 24

4-1-1 کاربران در لینوکس.. 24

4-1-2 نحوه تغییر اجازه‌‌های دسترسی. 25

4-2 دستورکار 27

آزمایش پنجم: سیستم فایل (سلسله مراتب فایل‌ها) 28

5-1 پیش‌آگاهی. 28

5-1-1 دایرکتوری ریشه (/) 28

5-1-2 دایرکتوری binary. 29

5-1-3 دایر کتوری configuration. 30

5-1-4 دایرکتوری data (بعدی ها زیر این هستند؟) 31

5-1-5 دایرکتوری in memory. 32

5-2 دستورکار 37

آزمایش ششم: مدیریت فرآیندها (مقدمه) 38

6-1 پیش‌آگاهی. 38

6-1-1 مشاهده مشخصات فرآیندها: فرمان‌های ps و top. 38

6-1-2 اجرای فرآیندها در پس‌‌زمینه (background) 38

6-1-3 هستة Linux. 39

6-1-4 پوستة Linux. 39

6-2 برنامه‌نویسی. 39

6-2-1 gcc (مترجم زبان C) 40

6-2-2 اشکالزدایی با gdb. 40

6-3 اتصال راه دور به ‌‌سیستم‌عامل لینوکس.. 41

6-4 دستورکار 43

آزمایش هفتم: مدیریت فرآیندها (حالات فرآیند و ایجاد فرآیند) 44

7-1 پیش‌آگاهی. 44

7-1-1 حالات مختلف فرآیند (Process) 44

7-1-2 ایجاد فرآیند جدید 45

7-1-3 اجرای عملیاتی متفاوت با عملیات فرآیند اصلی در فرآیند ایجاد شده 46

7-1-4 متوقف کردن یک فرآیند 48

7-1-5 راهنمایی‌هایی برای انجام آزمایش.. 48

7-2 دستورکار 49

آزمایش هشتم: چندنخی.. 50

8-1 پیش‌آگاهی. 50

8-1-1 مفهوم چند وظیفه‌‌ای و چندنخی. 50

8-1-2 امکانات لینوکس برای کار با نخ‌ها 50

8-2 دستورکار 51

آزمایش نهم: مدیریت نخ‌ها (هماهنگی بین نخ‌ها) 53

9-1 پیش آگاهی. 53

9-1-1 توابع مربوط به هماهنگی نخ‌ها 53

9-2 دستورکار 54

آزمایش دهم: مدیریت فرآیندها (ارتباط و هماهنگی بین فرآیندها) 56

10-1 پیش‌آگاهی. 56

10-1-1 لوله (Pipe) 56

10-1-2 دوطرفه‌کردن (Dup) 58

10-1-3 سیگنال‌ها و فراخوانی آن‌ها در Linux. 60

10-1-4 الگوریتم مرتب‌سازی موازی زوج‌فرد (OddEven) 60

10-2 دستورکار 62

10-2-1 آزمایش‌های مربوط به لوله و Dup. 62

10-2-2 آزمایش‌های مربوط به بخش ‌‌signal 62

10-2-3 بخش اختیاری آزمایش (الگورریتم Oddeven Sort) 62


آزمایش اول: آشنایی با ماشین مجازی و نصب لینوکس

1-1 پیش‌آگاهی

در این جلسه با ماشین مجازی و نحوه نصب ‌‌سیستم‌عامل Ubuntu روی ماشین مجازی آشنا می‌شوید.

1-1-1 ماشین مجازی

ماشین مجازی روشی برای ایجاد کامپیوترهای مجازی متعدد روی یک بستر سخت‌افزاری است. برای ماشین مجازی می‌توان دو دسته کاربرد در نظر گرفت:

الف) استفاده بهینه از سخت‌افزار

در کامپیوترهایی که حجم سخت‌افزار انبوهی دارند و معمولا در سرویس‌دهنده‌های وب استفاده می‌شوند (مثلا کامپیوترهای سرور که عموما دارای بیش از 100 هسته پردازشی، حجم عظیم حافظه RAM و دیسک سخت هستند)، برای استفاده بهینه از این حجم سخت‌افزار، ماشین‌های مجازی متعدد ایجاد می‌شوند که هر کدام بخشی از سخت‌افزار واقعی را در اختیار دارند. این کار در واقع صرفه‌جویی در فضای فیزیکی است (زیرا مثلا به جای استفاده از 100 کامپیوتر فیزیکی و مجزا، یک کامپیوتر قوی که تبدیل به 100 کامپیوتر مجازی شده است استفاده می‌شود).

ب) شبیه‌سازی ‌‌سیستم‌عامل‌های مختلف روی یک ماشین میزبان

در موارد متعدد نیاز است که چندین ‌‌سیستم‌عامل روی یک کامپیوتر نصب شوند. نصب همزمان این سیستم‌ها به صورت واقعی روی دیسک ممکن است امکان‌پذیر نباشد و با ریسک‌هایی روبرو است؛ به خصوص وقتی که نصب ‌‌سیستم‌عامل جدید به صورت موقت باشد و بعد از مدتی قصد حذف کردن آن را داشته باشید. به عنوان مثال می‌توان موارد زیر را ذکر کرد:

- قصد اجرا اجرای نرم‌افزاری را داشته باشید که در ‌‌سیستم‌عامل کنونی اجرا نمی‌شود.

- قصد اجرای برنامه‌‌ای را داشته باشید که ممکن است عملکرد ‌‌سیستم‌عامل را مختل کند.

- قصد یادگیری ‌‌سیستم‌عامل جدیدی را داشته باشید (علت استفاده از ماشین مجازی در این آزمایشگاه نیز همین است).

ماشین مجازی به دو صورت پیاده‌سازی می‌شود:

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

- در روش دوم، از ‌‌سیستم‌عامل میزبان استفاده می‌شود. در این روش، در واقع یک برنامه کاربردی روی ‌‌سیستم‌عامل میزبان اجرا می‌شود که ماشین‌های مجازی را پیاده‌سازی می‌کند. برنامه vmware که در ‌‌سیستم‌عامل ویندوز قابل استفاده است و همچنین برنامه VirtualBox که در ‌‌سیستم‌عامل‌های ویندوز و لینوکس قابل استفاده است، از این نوع هستند.

1-1-2 معرفی سیستم‌عامل Linux و نگاهی به ویژگی‌های آن

برای معرفی بهتر سیستم‌عامل Linux، ابتدا با تاریخچة سیستم‌عامل Unix و انواع مختلف آن آشنا می‌شویم. سپس تاریخچة سیستم‌عامل Linux را بررسی می کنیم.

 

تاریخچه سیستم‌عامل Unix

سیستم‌عامل Unix در سال 1969 میلادی توسط کن تامپسون و دنیس ریچی، بر روی کامپیوتر DEC PDP-7 در آزمایشگاه‌های بل با زبان اسمبلی طراحی شد. در سال‌های بعد تامپسون و ریچی، Unix را با زبان C بازنویسی کردند. زبان C قابل حمل بود وکمک کرد که Unix به ‌‌سیستم‌عاملی تبدیل شود که می‌توانست بر روی انواع متفاوتی از کامپیوترها اجرا گردد. توسعة سیستم‌عامل به آزمایشگاه‌های بل محدود نشد، بلکه در اواسط دهة 1970، Unix یک محصول تحقیقاتی بود که دانشگاه‌های بسیاری بر روی آن کار می‌کردند. از آن زمان تا کنون کارهای بسیاری بر روی Unix انجام گرفته است. با پیشرفت صنعت پردازنده‌ها و ظهور کامپیوترهای قوی‌تر، گونه‌های متعددی از آن بر روی کامپیوترهای گوناگون، توسط شرکت‌ها و مراکز تحقیقاتی مختلف دنیا ارائه شده است؛ مانند: Solaris برای پردازنده‌های Sparc، OSF/1 برای پردازنده‌های Alpha و Linux برای پردازنده‌های 80x86 (که در این آزمایشگاه مورد توجه ما است).

به‌طور کلی ویژگی‌های زیر را برای انواع مختلف سیستم‌عامل Unix می‌توان برشمرد:

طراحی مستقل از سخت‌افزار: از آنجا که قسمت اعظم کُد این سیستم‌عامل به جای اینکه با اسمبلی نوشته شود، به زبان C نوشته شده است، این سیستم‌عامل در مقایسه با سیستم‌عامل‌های دیگر دارای سرعت کم‌تر، ولی انعطاف و اطمینان بیشتری است. نگهداری چنین سیستم‌عاملی برای تولیدکنندگان آن آسان‌تر از سیستم‌عاملی است که با زبان اسمبلی تولید شده باشد. به همین دلیل روایت‌های زیادی از این سیستم‌عامل بر روی سخت‌افزارهای مختلف ایجاد شده است.

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

چندوظیفه‌ای بودن: هر کاربر می‌تواند همزمان چند وظیفة مختلف انجام دهد (مثلا چند برنامه مختلف را به طور همزمان اجرا کند).

امکان شبکه شدن به صورت ذاتی: قابلیت اتصال کامپیوترهای کوچک و بزرگ و ایجاد شبکه‌های کامپیوتری، در نهاد این سیستم‌عامل تعبیه گشته است و برای ایجاد شبکه، احتیاجی به مدیر شبکة دیگری نیست.

دارا بودن پایانه‌های متنی وگرافیکی: امروزه، اکثر سیستم‌های Unix دارای پایانه‌های متنی یا گرافیکی هستند. همان‌طورکه گفته شد، در آغاز Unix برای کامپیوترهای بزرگ طراحی شد. این کامپیوترها متشکل از یک یا چند پردازندة قوی مرکزی و تعدادی پایانه (Terminal) بودند. دسترسی همزمان به کامپیوتر از طریق این پایانه‌ها انجام می‌گرفت. در واقع برای استفاده از قدرت چندکاربره بودن Unix، وجود این پایانه‌ها لازم بود. اولین گونه‌های این سیستم‌عامل دارای پایانه‌های متنی بودند. یک پایانة متنی، دارای یک قالب متنی است که کوچک‌‌ترین جزء قابل تفکیک آن یک کاراکتر است (مانند محیط متنی DOS). با پیشرفت کامپیوترها و گرایش کاربران به واسط‌های کاربر گرافیکی، تحول بزرگی در زمینة پایانه‌های Unix به وقوع پیوست: ظهور X Window System (یا به طور مخفف X). واسط گرافیکی X Window را می‌توان به محیط Microsoft Windows تشبیه کرد: X یک سیستم پنجره‌بندی گرافیکی است که یک واسط گرافیکی منویی و کاربرپسند (User Freindly) ارائه می‌کند. از ویژگی‌های بارز این واسط کاربر، سهولت و سادگی ارتباط کاربر با کامپیوتر است. در واسط‌های گرافیکی، کوچک‌‌ترین عنصر قابل تفکیک صفحة نمایش، یک نقطه (Pixel) است. محیط X دارای قابلیت‌های فراوانی است که در اینجا از ذکر آنها صرف‌نظر می‌شود.

تاریخچة سیستم‌عامل Linux

پیشرفت صنعت ریزپردازنده‌ها و ظهور ریزکامپیوترهای سریع و قوی، زمینه را برای به وجود آمدن گونه‌ای از Unix بر روی ریزکامپیوترها آماده ساخت. در مارس 1991 میلادی، Linus Torvalds یک سیستم Minix برای کامپیوتر 386 خود خرید تا از آن برای طراحی سیستم‌عامل چند وظیفه‌ای خود استفاده کند. در ماه سپتامبر همان سال، او اولین نگارش آمادة سیستم‌عامل خود را بر روی شبکة اینترنت قرار داد و از تمام علاقه‌مندان، برای تکمیل آن دعوت به همکاری کرد. این سیستم‌عامل که ایدة خود را از Unix گرفته بود، Linux نام گرفت. از این تاریخ، بسیاری از برنامه‌نویسان سراسر دنیا به کار بر روی Linux پرداختند. به این ترتیب پروژة Linux آغاز شد و به تدریج قسمت‌های مختلف آن شکل گرفت و کامل گشت. اکنون این سیستم‌عامل بر روی ریزکامپیوترهای مبتنی بر پردازنده‌های 80x86 موجود است. در این بین، مؤسساتی مانند Free Software Foundation نیز مسؤولیت پشتیبانی و ارتقای Linux را بر عهده گرفته‌اند.

به‌طور کلی ویژگی‌هایی Linux را می‌توان به صورت زیر برشمرد:

- چند وظیفه‌ای و چند کاربره بودن، ‌همچنین امکان شبکه شدن (که پیش‌‌تر برای انواع Unix بر شمردیم)، از جمله ویژگی‌های Linux است. با این سیستم‌عامل می‌توان شبکه‌های بسیار قوی طراحی کرد که امکان اتصال به شبکه‌های دیگر را نیز داشته باشند.

- Linux امکان استفاده از پایانه‌های متن و همچنین پایانه‌های گرافیکی مبتنی بر استاندارد X را دارد.

- این سیستم‌عامل را می‌توان بر روی یک کامپیوتر منفرد و جدا از شبکه نیز به‌کار گرفت و برای کار با آن وجود یک شبکة متشکل از چندین ریز کامپیوتر الزامی نیست. البته در این حالت دیگر نمی‌توان از امکانات شبکه‌ای Linux استفاده کرد.

توزیع‌های مختلف لینوکس

امروزه صدها توزیع Linux در بازار موجود است. سه خانواده ی اصلی توزیع‌های ‌‌سیستم‌عامل لینوکس عبارتند از:

- خانواده‌ی توزیع‌های Debian همچون توزیع Ubuntu

- خانواده‌ی توزیع‌های SUSE همچون openSUSE

- خانواده‌ی توزیع‌های Fedora همچون CentOS

راهنمای سیستم

به همراه گونه‌های جدید Unix و از جمله Linux، بستة جامع راهنمای سیستم تحت عنوان صفحة راهنما (Manual Page) ارائه شده است. هر جا که در کار کردن با امکانات سیستم‌عامل به اشکال برخوردید، سعی کنید از این صفحات راهنما با استفاده از فرمان man کمک بگیرید. این فرمان حداقل به یک پارامتر نیاز دارد و آن کلمة کلیدی (keyword) درخواستی است. مثلاً برای گرفتن اطلاعات در مورد دستور ls باید فرمان زیر را صادر کنید:

$ man ls

این مجموعه راهنما در نه فصل سازماندهی شده است که سه فصل اول آن در این آزمایشگاه به کار می‌آید:

- فصل اول مربوط به فرمان‌هایی است که در اختیار کاربر قرار دارند.

- فصل دوم مربوط به کتابخانة استاندارد فراخوانی‌های سیستم (system calls) در Linux است.

- فصل سوم توابع کتابخانة زبان C را شرح می‌دهد.

می‌توانید با گزینة S - به man بگویید که در کدام فصل، موضوع را جستجو کند. مثلا فرمان زیر در فصل سوم راهنماها به دنبال دستور exit (که در زبان C استفاده می‌شود) می‌گردد:

$ man -S 3 exit

ویرایشگرها

ویرایشگرهایی که در سیستم‌عامل Linux می‌توانید استفاده کنید عبارتند از vi، emacs و xemacs، vim و nano. ویرایشگرهای vi و vim برای کاربران حرفه‌ای ‌‌سیستم‌عامل Linux است و کار کردن با آن‌ها مخصوصا برای کسانی که با ‌‌سیستم‌عامل ویندوز کار کرده اند مشکل است. به همین خاطر در این آزمایشگاه تا حد ممکن از آن‌ها استفاده نخواهیم کرد. کار کردن با ویرایشگر nano در مقایسه با سایر ویرایشگرهای Linux آسان‌‌تر است (اگرچه ممکن است بازهم در کار با آن راحت نباشید). این ویرایشگر در محیط متن اجرا می‌شود ولی می‌تواند خود را با محیط گرافیکی هم تطبیق دهد. برای کار کردن با nano دانستن عملکرد چند کلید بسیار مفید است. برخی از این کلیدها در جدول 1-1 فهرست شده است.

جدول 1-2: چند کلید مفید در ویرایشگر nano

عملکرد

کلید

ذخیره فایل

Ctrl + O

خروج از ویرایشگر

Ctrl + X

رفتن به صفحه قبل

Ctrl + Y

رفتن به صفحه بعد

Ctrl + V

انتخاب متن

Alt + A

بریدن متن انتخاب شده

Ctrl + K

کپی کردن متن انتخاب شده

Alt + 6

چسباندن متن بریده شده یا کپی شده

Ctrl + U

نمایش مکان فعلی (شماره خط، سطر ..)

Ctrl + C


1-2 دستورکار

1-فایل iso از ‌‌سیستم‌عامل Ubuntu را از مربی آزمایشگاه دریافت کنید و روی ماشین مجازی نصب نمایید (مراحل نصب در بخش 1-4 آورده شده است).

2- ‌‌سیستم‌عامل نصب شده را اجرا کنید و موارد زیر را تمرین کنید:

1-2 دسترسی به فایل‌ها و پوشه‌‌ها (با استفاده از آیکون کابینت )

2-2 باز کردن فایل‌های Microsoft Office

3-2 جستجوی فایل‌ها و برنامه‌ها

4-2 نمایش تاریخ و تنظیم صدا و تغییر زبان

5-2 خاموش و restart و قفل نمودن

6-2 ساخت و مدیریت User‌‌ها

7-2 تغییر رمز عبور

3- با فشردن کلیدها Ctrl + Alt + t یک ترمینال باز کنید. سپس با استفاده از دستور nano، ویرایشگر را اجرا کنید و دستورات موجود در جدول 1-2 را تمرین کنید.

1-3 مراحل نصب ‌‌سیستم‌عامل Ubuntu در VMware

نرم‌افزار VMware را اجرا و سپس گزینه مشخص شده را انتخاب کنید:

اکنون مراحلی که در ادامه نشان داده شده است را طی کنید.

 

 

http://laptops.eng.uci.edu/_/rsrc/1467408286192/software-installation/using-linux/how-to-set-up-virtual-linux/vm-7.jpg

 

http://laptops.eng.uci.edu/_/rsrc/1467408286192/software-installation/using-linux/how-to-set-up-virtual-linux/vm-9.jpg

 

http://laptops.eng.uci.edu/_/rsrc/1467408286191/software-installation/using-linux/how-to-set-up-virtual-linux/vm-10.jpg

 

http://laptops.eng.uci.edu/_/rsrc/1467408286191/software-installation/using-linux/how-to-set-up-virtual-linux/vm-11.jpg

 

http://laptops.eng.uci.edu/_/rsrc/1467408286191/software-installation/using-linux/how-to-set-up-virtual-linux/vm-12.jpg

حالا ‌‌سیستم‌عامل Ubuntu نصب شده است.

 

1-4 سوال

·         هشت توزیع مختلف لینوکس را توضیح دهید و کاربردهای برجسته هر یک را عنوان نمایید.

·         مزایا و معایب استفاده از ماشین‏های مجازی چیست؟

·         چرا پیشنهاد می‏شود که کاربرهای آماتور از کاربر root استفاده نکنند؟

·         لینوکس را با ویندوز مقایسه کنید و مزایا و معایب هر یک را بیان کنید.


آزمایش دوم: سیستم فایل: مدیریت دایرکتوری

2-1 پیش‌آگاهی

سیستم فایل، یکی از بخش‌های مهم هر ‌‌سیستم‌عاملی است. در این آزمایش و آزمایش بعدی با سیستم فایل ‌‌سیستم‌عامل لینوکس آشنا می‌شوید.

2-1-1 سیستم فایل (File System)

‌‌سیستم‌عامل Linuxنیز همانند همة سیستم‌های ‌عامل‌های دیگر به طرز چشمگیری بر اطلاعات ذخیره شده در پرونده‌ها تکیه می‌کند: اطلاعات کاربران مختلف، پرونده‌های اجرایی مورد نیاز کاربران، پرونده‌های داد‌های مربوط به آن‌ها، کتابخانه‌های مورد نیاز برای برنامه‌نویسی، اطلاعات مربوط به تنظیم‌های سخت‌افزاری و امکانات موجود در سیستم، کُد اجرایی خود سیستم‌عامل و بسیاری اطلاعات دیگر همگی به صورت پرونده ذخیره می‌شوند. بنابراین با توجه به اهمیت و حساسیت اطلاعات فوق الذکر لازم است که این پرونده‌ها تحت یک نظام قوی و قابل اطمینان مدیریت و نگهداری شوند. در سیستم‌های عامل، انجام این وظایف بر عهدة سیستم پرونده است. مثلاً در DOS سیستم پروندة FAT و در سیستم‌عاملWindows ، سیستم پروندة NTFS برای این کار طراحی شده‌اند. گونه‌هایی از Unix که قبل از BSD نگارش 2/4 ایجاد شده‌اند، هر یک سیستم پروندة مربوط به خود را داشتند. یکی از ویژگی‌های جالب توجه سیستم‌عامل Linux در نگارش‌های System V Release 4 به بعد این است که سیستم پرونده آن انواع سیستم‌های پروندة موجود را می‌شناسد و قادر است اطلاعات موجود در پرونده‌‌هایشان را بخواند. سیستم‌های پروندة پراستفاده در Unix عبارتند از: Extended File System 2 و System V File System. صرف نظر از نوع، سیستم پرونده باید اطلاعاتی را که سیستم‌عامل برای شناسایی کامل یک پرونده نیاز دارد مهیا کند.

 
 

سیستم فایل در linux برخلاف سیستم فایل در ویندوز که می‌تواند شامل چند درایو منطقی باشد (نظیرa:،b:،c: و)، تنها شامل یک بخش است که آن هم چون فقط یکی است، نام خاصی ندارد. شاخه ریشه این درایو با علامت / شناخته می‌شود و تمام فایل‌ها و دایرکتوری‌های linuxدرشاخه /قرار می‌گیرند. شاخه‌‌‌های اصلی سیستم که مستقیما درشاخه / قرار دارند، عبارتنداز: home ,lib ,etc, bin ,root , mnt وچند شاخه دیگر. در اینجا ما تنها اشاره‌ای به شاخه‌های root و home می‌کنیم. ساختار درختی دایرکتوری‌های لینوکس در شکل 2-1 نمایش داده شده است. در آزمایش پنجم، جزئیات ‌‌بیش‌تر این ساختار بررسی خواهد شد.

شکل 2-1: ساختار درختی دایرکتوری‌ها در لینوکس

برخی از دستورات لینوکس در مدیریت دایرکتوری‌ها در جدول 2-1 نمایش داده شده‌اند.

 

جدول 2-1: دستورات لینوکس برای مدیریت دایرکتوری‌ها

توضیحات

قالب فرمان

تغییر شاخة جاری

cd path

گرفتن لیست پرونده‌ها و شاخه‌ها

ls path

انتفال پرونده‌ها وشاخه‌ها

mv path/filename newpath

کپی کردن پرونده‌ها وشاخه‌ها

cp source-file target-file

حذف شاخه‌ها

rmdir directory-name

ایجاد شاخه‌ها

mkdir directory-name

یافتن پرونده‌ها و نمایش آن‌ها

find path -name filename- print

دیدن محتویات پرونده‌‌ها

cat path/filename

حذف پرونده‌‌ها

rm path/filename

دیدن صفحه به صفحة پرونده‌‌ها

more path/filename

نمایش مسیرکامل شاخه فعلی

pwd

 

Manual Pages

      man $command

      man $configfile

      man $daemon

      man -k (apropos)

      whatis

      whereis

      man man

Working with directories

      pwd

      cd

      cd $directory

      cd or cd ~

      cd ..

      cd .

      cd -

دایرکتوری جاری مکان شروع حساب می‌شود و نیازی به نوشتن / در مسیر نیست.

      ls

      -a --all all files even hidden ones

      -d --directory folder details

      -h --human readble

      -l long format

      -r --reverse decreasing alphabet order

      -s size

      -t last time of modifying

      ls

      ls -a

      ls -l

      ls -lh

 

      mkdir mydir

      mkdir -p

      rmdir emptydirectory

      rmdir -p

2-2 دستورکار

1) اطلاعات داخل (لیست) دایرکتوری جاری خود را نمایش دهید. به زیر شاخه /etc بروید و سپس دستور cd را بدون پارامتر اجرا کنید. چه اتفاقی می‌افتد؟

2) به دایرکتوری /etc تغییر مکان دهید و دستور ls را با پارامتر‌های زیر اجرا کنید (سعی کنید با استفاده از مشاهدات خود و دستور man، تاثیر هر پارامتر را بدست آورید).

ls –a

ls –l

ls –lh

3) دوباره به دایرکتوری /etc تغییر مکان دهید و با استفاده از فشردن تنها سه کلید از صفحه کلید، به دایرکتوری home بروید.

4) با استفاده از فشردن تنها 11 بار کلیدهای صفحه کلید، به دایرکتوری /boot/grub بروید.

5) به دایرکتوری والد دایرکتوری جاری بروید.

6) به دایرکتوری ریشه بروید.

7) محتویات داخل دایرکتوری root را لیست کنید.

8) در دایرکتوری جاری باقی بمانید و لیست دایرکتوری /etc را نمایش دهید.

9) در دایرکتوری جاری باقی بمانید و لیست دایرکتوری‌های /bin و /sbin را نمایش دهید.

10) کلیه فایل‏های موجود در دایرکتوری home (شامل فایل‏های مخفی) را نمایش دهید.

11) فایل‏های موجود در دایرکتوری /boot را به گونه‏ای نمایش دهید که برای انسان خواناتر باشد.

12) یک دایرکتوری با نام testdir در دایرکتوری home خود ایجاد کنید. یک فایل متنی خالی در این مسیر قرار دهید.

13) به دایرکتوری /etc منتقل شوید، در آنجا بمانید و در home، دایرکتوری جدیدی به نام newdir ایجاد کنید.

14) سه دایرکتوری ~/dir1/dir2/dir3 را به کمک یک دستور ایجاد کنید.

15) دایرکتوری testdir را حذف کنید.

16) تفاوت man،help و info چیست؟


آزمایش سوم: سیستم فایل: مدیریت فایل‌ها

3-1 پیش‌آگاهی

در آزمایش‌ قبل با نحوه مدیریت دایرکتوری‌ها آشنا شدید. در این آزمایش با نحوه مدیریت فایل‌ها در لینوکس آشنا می‌شوید. مدیریت فایل‌ها شامل ایجاد، مشاهده، ویرایش، حذف، کپی و انتقال فایل‌ها است. در جدول 3-1 لیستی از دستورات لینوکس در مدیریت فایل‌ها نمایش داده شده است.

 

جدول 3-1: دستورات لینوکس برای مدیریت فایل‌ها

توضیحات

قالب فرمان

 

file filename

 

touch filename

 

rm filename

 

rm -rf

 

cp source targetfile/targetdirectory

 

cp -r

 

cp –i

 

mv trgtfile/trgtdir newname

 

head filename

 

head -n filename

 

head -cn filename

 

tail filename

 

tail -n filename

 

cat filename

 

cat filename1 filename2 …

 

3-1-1 انواع فایل‌ها در لینوکس

در Linux انواع مختلفی از پرونده‌‌ها وجود دارد. به هر نوع یک حرف کوچک انگلیسی متناظر اختصاص داده شده است. کاربرد این حروف را بعدا در توضیحات دستور ls خواهید دید. انواع مختلف پرونده به همراه حروف متناظرشان در جدول 3-1 فهرست شده‌اند.

 

 

جدول 3-1: انواع پرونده‌‌های موجود در Linux

حرف متناظر

نوع پرونده

-

ordinary file

d

directory

p

fifo

c

character device

b

block device

l

link to another file

 

نوع پرونده ordinary file، اغلب شامل پرونده‌‌های متن و پرونده‌‌های داد‌ه‌ای می‌شود. مثلاً پرونده‌های برنامه‌های زبان C از این نوع هستند.

نوع پرونده directory نیز که برای کاربران DOS و Windows، نوعِ پرونده شناخته شده‌ای است، برای دسته‌بندی مجموعه‌های پرونده استفاده می‌شود.

نوع پرونده fifo نوعی پرونده است که برای برقراری ارتباط بین فرآیندها استفاده می‌شود. یکی از راه‌های تبادل اطلاعات بین فرآیند‌ها در Linux، استفاده از fifo است. ویژگی این کانال ارتباطی این است که پس از قطع ارتباط بین فرآیندها از بین نمی‌رود و پایدار باقی می‌ماند.

قبل از توضیح دادن نوع‌های character device و block device لازم است مطالبی راجع به نحوة استفادة Linux از سخت‌افزار بدانید. سیستم‌عامل Linux با هر سخت‌افزاری به صورت یک پرونده برخورد می‌کند. مثلاً ارسال یک بلوک داده به چاپگر معادل نوشتن آن بلوک داده در پروندة متناظر با چاپگر است. به این پرونده، پروندة دستگاه (device file) گفته می‌شود. عملیات ورودی و خروجی در سخت‌افزارهای مختلف به دو صورت انجام می‌شود:

- ارسال و دریافت بایت به بایت داده‌ها.

- ارسال و دریافت بلوکی داده‌ها (در یک انتقال بیش از یک بایت منتقل شود).

 

متناظر با این تقسیم بندی، پرونده‌های دستگاه نیز به دو دسته تقسیم می‌شوند:

- :character device مانند درگاه سری (Serial Port). پرونده‌های این توع را با حرف c نشان می دهند.

- :block device مانند دیسک ‌سخت. پرونده‌های این توع را با حرف b نشان می دهند.

بعدا دربارة این پرونده‌ها مطالب بیشتری خواهیم دانست.

 

مالک پرونده: مالک پرونده کسی است که پرونده را ایجاد می‌کند. مالک پرونده می‌تواند آن را به مالکیت کاربران دیگر درآورد. مدیر سیستم و کاربران ایجاد کنندة پرونده، نمونه‌هایی از مالکان پرونده هستند.

گروه پرونده: در سیستم‌عامل Linux کاربران به گروه‌هایی تقسیم می‌شوند. برای یک پرونده علاوه بر مالک آن شمارة گروهی از کاربران که می‌توانند به آن پرونده دسترسی داشته باشند نیز نگهداری می‌شود.

اجازه‌های دسترسی (Access Permissions): اجازه‌ دسترسی نحوة دسترسی افراد به پرونده‌ها را مشخص می‌کند. سه دسته از افرادی که می‌توانند به یک پرونده دسترسی داشته باشند، عبارتند از:

- مالک پرونده

- افراد درون گروه مربوط به آن پرونده

- بقیة کاربران

برای هر کدام از افراد فوق سه نوع اجازة دسترسی مطرح می‌شود:

- خواندن (Read)

- نوشتن (Write)

- اجرا کردن (eXecute)

بنابراین همراه اطلاعات هر پرونده، نُه اجازة دسترسی نگهداری می‌شود. صاحب پرونده و همچنین، مدیر سیستم می‌توانند این اجازه‌ها را تغییر دهند. اگر کاربری اجازة نوشتن در یک شاخه را نداشته باشد، آن کاربر نمی‌تواند به پرونده‌های درون آن شاخه دسترسی داشته باشد. برای دیدن اجازه‌های منتسب به یک پرونده دستور ls -l به کار می‌رود. در آزمایش چهارم به صورت مفصل در زمینه اجازه‌های دسترسی صحبت خواهد شد.

تاریخ: تاریخ اعمالِ آخرینِ تغییر و آخرین دسترسی به پرونده نیز در مشخصات آن نگهداری می‌شود.

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

نام پرونده: در نگارش استاندارد Unix، حداکثر طول نام پرونده 14 کاراکتر است، اما بسته به نوع سیستم پرونده، این قابلیت وجود دارد که تا 256 کاراکتر برای نام پرونده ذخیره شود. درLinux نام پرونده می‌تواند 256 کاراکتر داشته باشد و تنها نویسة غیرمجاز در نام پرونده ‘/’ است. این کاراکتر جداکنندة نام شاخه‌ها در مسیردهی به نام یک پرونده است؛ مثلاً: /usr/X11/bin/xinit.

3-1-2 ویرایشگر vi

در آزمایش اول با ویرایشگر nano، برای ایجاد یک فایل متنی آشنا شدید. این ویرایشگر از لحاظ ورود متن خیلی مشابه ویرایشگرهایی است که در ‌‌سیستم‌عامل‌های دیگر تجربه کرده‌اید. با این وجود این ویرایشگر در تمام توزیع‌های لینوکس وجود ندارد. در این بخش قصد داریم شما را با ویرایشگر vi آشنا کنیم. این ویرایشگر جزء قوی‌‌ترین ویرایشگرهایی است که در تمام توزیع‌های لینوکس و تمام نسخه‌های آن‌ها وجود دارد و برخی مواقع تنها ویرایشگری است که می‌توانید استفاده کنید.

با اجرای دستور vi filename وارد ویرایشگر خواهید شد. این ویرایشگر دارای دو حالت کاری، به نام‌های فرمان و درج است. در حالت فرمان، شما می‌توانید فرامینی نظیر حذف یک خط، حذف یک کلمه، رفتن سر کلمه بعدی، جستجوی متن، رفتن به صفحه بعد یا صفحه قبل را به ویرایشگر بدهید. در حالت درج، می‌توان کاراکترها را به متن اضافه کرد. برای خروج از حالت درج، کافی است کلید ESC را فشار دهید. برای رفتن به حالت درج باید اول کلید : و سپس کلید i و سپس کلید Enter را بزنید.

برای ذخیره کردن باید به حالت فرمان بروید و سپس :w را تایپ کنید و سپس کلید Enter بزنید. برای خروج از ویرایشگر باید به حالت فرمان بروید (فشردن کلید ESC) و سپس :q را تایپ کرده و Enter بزنید.

برخی از فرامین ادیتور vi در جدول 3-2 لیست شده‌اند.

 

جدول 3-2: برخی از فرامین ویرایشگر vi

فرمان

توضیحات

:q

خروج از ویرایشگر

:w

ذخیره کردن

:i

رفتن به حالت درج

x

حذف کاراکتر (البته باید در حالت فرمان باشید)

dd

حذف خط جاری

:r filename<Enter>

لود کردن فایل

:w newfile<Return>

ذخیره کردن در یک فایل تحت یک نام جدید

:12,35w smallfile<Return>

ذخیره خط 12 تا 35 در یک فایل جدید

/string

جستجوی یک رشته

yy

کپی کردن خط جاری

p

درج خط کپی شده در محل جاری

 

3-2 دستورکار

1) فایل‏های موجود در دایرکتوری /bin را نمایش دهید.

2) نوع فایل‏های /bin/cat ، /etc/passwd و /usr/bin/passwd را نشان دهید.

3) یک دایرکتوری با نام /touched در دسکتاپ خود ایجاد کنید و وارد آن دایرکتوری شوید.

4) فایل‏های today.txt و yesterday.txt را در دایرکتوری ایجاد شده در گام قبل، ایجاد نمایید.

5) فایل yesterday.txt را در فایلی به نام copy.yesterday.txt کپی کنید.

6) نام فایل copy.yesterday.txt را به new تغییر دهید.

7) دایرکتوری دیگری تحت عنوان /testbackup در دسکتاپ ایجاد کنید و تمام فایل‏های درون دایرکتوری touched را در آن کپی کنید.

8) از یک دستور استفاده کنید و دایرکتوری /testbackup را به همراه تمام فایل‏های درون آن حذف کنید.

9) یک دایرکتوری /etcbackup روی دسکتاپ ایجاد کنید و تمام فایل‏هایی با پسوند .conf از پوشه /etc را در آن کپی کنید.

10) دوازده خط اول /etc/services را نمایش دهید.

11) خط آخر /etc/passwd را نمایش دهید.

12) به کمک دستور cat فایلی با نام count.txt ایجاد کنید که محتویات آن به صورت زیر باشد:

one

Two

Three

13) به کمک چه دستورهایی می‏توان محتویات داخل فایل count.txt را در فایل newcount.txt کپی کرد؟

14) با راهنمایی مربی آزمایشگاه، با استفاده از ویرایشگر vi، یک فایل ایجاد کنید و متن زیر را داخل آن تایپ کنید و به نام viexample.txt ذخیره نمایید.

This is a sample Text

What is your opinion about vi text editor?!!!

 

 


4-1 پیش‌آگاهی

در این آزمایش با مفهوم مجوزهای دسترسی به فایل‌ها و دایرکتوری‌‌ها و نحوه تغییر آن‌ها آشنا خواهید شد.

4-1-1 کاربران در لینوکس

همانند ‌‌سیستم‌عامل ویندوز که دو دسته کاربر دارد (کاربران عادی و مدیرسیستم با نام خاص AdministratorLinux نیز همین دو دسته کاربر را دارد. در linux نام خاص کاربری برای مدیر سیستم به جای administrator، root است. بنابراین شاخه /root شاخه مخصوص مدیر سیستم است وlinux اجازه ورود و تغییر محتویات این شاخه را به هیچ کاربری به غیر از مدیر سیستم نمی‌دهد و مدیر سیستم می‌تواند فایل‌ها و دایرکتوری‌های غیرمجاز برای مشاهده دیگران را در این شاخه قرار دهد.

همچنین برای هر کاربر جدیدی (نام کاربری جدید مثل user1) که توسط مدیرسیستم در linux ثبت می‌شود، یک دایرکتوری جدید با نام کاربری او در شاخه /home ایجاد می‌شود (مثلاً /home/user1) که وقتی کاربر به سیستم login می‌کند، این شاخه به عنوان شاخه جاری او می‌باشد (که البته قابل تغییراست). قابل توجه است که اگر مثلاً کاربرuser1 فایل‌ها و دایرکتوری‌‌هایی رادرشاخه /home/user1 ذخیره کند، هیچ کاربر دیگری اجازه ورود به این شاخه و دیدن محتویات آن را ندارد مگرroot که اجازه ورود و تغییرهمه دایرکتوری‌های سیستم را دارد. در ‌‌سیستم‌عامل لینوکس، برای هر پرونده، دو مفهوم مالک و گروه تعریف خواهد شد که توضیحات آن به صورت زیر است:

مالک پرونده: مالک پرونده کسی است که پرونده را ایجاد می‌کند. مالک پرونده می‌تواند آن را به مالکیت کاربران دیگر درآورد. مدیر سیستم و کاربران ایجاد کنندة پرونده، نمونه‌‌هایی از مالکان پرونده هستند.

گروه پرونده: در سیستم‌عامل Linux کاربران به گروه‌هایی تقسیم می‌شوند. برای یک پرونده، علاوه بر مالک آن شمارة گروهی از کاربران که می‌توانند به آن پرونده دسترسی داشته باشند نیز نگهداری می‌شود.

اجازه‌‌های دسترسی (Access Permissions): نحوة دسترسی افراد به پرونده‌‌ها را مشخص می‌کند. سه دسته از افرادی که می‌توانند به یک پرونده دسترسی داشته باشند، عبارتند از:

مالک پرونده

افراد درون گروه مربوط به آن پرونده

بقیة کاربران

برای هر کدام از افراد فوق سه نوع اجازة دسترسی مطرح می‌شود:

خواندن (Read)

نوشتن (Write)

اجرا کردن (eXecute)

بنابراین همراه اطلاعات هر پرونده 9 اجازة دسترسی نگهداری می‌شود. صاحب پرونده و همچنین، مدیر سیستم می‌توانند این اجازه‌‌ها را تغییر دهند. اگر کاربری اجازة نوشتن در یک شاخه را نداشته باشد، آن کاربر نمی‌تواند به پرونده‌‌های درون آن دسترسی داشته باشد. برای دیدن اجازه‌‌های منتسب به یک پرونده دستور ls -l را بکار ببرید.

4-1-2 نحوه تغییر اجازه‌‌های دسترسی

با استفاده از دستور chmod (Change-mode) می‌توان اجازه‌‌های دسترسی مربوط به یک فایل یا دایرکتوری را تغییر داد. ساختار دستوری آن به صورت زیر است:

chmod [references][operator][modes] file1 ...

در ساختار این دستور، چهار نوع پارامتر مختلف وجود دارد. پارامتر اول references است که نشان‌دهنده کلاس کاربری است که قرار است سطح دسترسی آن تغییر کند. همان‌طورکه گفته شد، سه کلاس کاربری موجود است: user (که به اختصار با حرف u مشخص می‌شود)، group (که به اختصار با حرف g مشخص می‌شود) و others (که به اختصار با حرف o مشخص می‌شود). اگر کلاس کاربر در دستور مشخص نشد، به صورت پیش‌فرض کلاس all یا همه در نظر گرفته می‌شود (که به اختصار با حرف a مشخص می‌شود). یعنی تغییرات برای همه سطوح کاربری اعمال می‌شود. در جدول 4-1 مشخصات کلاس‌ کاربری و نحوه نمایش مختصر آن آورده شده است.

 

جدول 4-1: مشخصات کلاس‌ کاربری و نحوه نمایش مختصر آن

پارامتر دوم، عملگر یا operator است که دستور chmod از آن برای تنظیم مد فایل استفاده می‌کند. از علامت + برای اضافه کردن یک مد خاص به مدهای موجود فایل، از علامت برای حذف یک مد خاص از مجموعه مدهای موجود فایل و از علامت = برای انتساب مد به فایل استفاده می‌شود. در جدول زیر علامت‌های مربوط به عملگر و کاربردشان آورده شده است.

 

جدول 4-2: علامت‌های مربوط به عملگر و کاربرد آن‌ها

پارامتر سوم، مد است که مشخص‌کننده نوع دسترسی به فایل است و به نوع مشخصی از کاربران داده می‌شود یا از آن‌ها گرفته می‌شود. سه نوع مد تعریف شده است که عبارتند از: خواندن (که به اختصار با حرف r نشان داده می‌شود)، نوشتن (که به اختصار با حرف w نشان داده می‌شود) و اجرا (که با اختصار با حرف e نشان داده می‌شود). در جدول زیر مدها و مشخصات آن‌ها آورده شده است.

 

 

جدول 4-3: معرفی مدها و مشخصات آن‌ها

پارامتر سوم، نام فایلی (یا فایل‌هایی) است که باید مد آن تغییر کند. در ادامه نمونه‌هایی از نحوه به کارگیری دستور chmode آورده شده است:

Ø chmod g+r myfile

Ø chmod u-w myfile

Ø chmod g+r-w myfile

Ø chmod a=rw myfile

Ø chmod ug=rw myfile

Ø chmod =rw myfile

برای حالتی که از عملگر = استفاده می‌شود (یعنی می‌خواهیم ویژگی یا ویژگی‌هایی به فایل منتسب کنیم)، می‌توانیم از معادل باینری دستورات نیز استفاده کنیم. به این صورت که از چپ به راست کاربر user، group و other قرار می‌گیرد. برای هر کدام از کلاس کاربری سه نوع مد خواندن و نوشتن و اجرا از چپ به راست در نظر گرفته می‌شود. 1 معادل با اجرا، 2 معادل با نوشتن و 4 معادل با خواندن است. برای هر کلاس کاربری، مقدار باینری محاسبه می‌شود و مقدار باینری محاسبه شده، جلوی دستور chmod قرار می‌گیرد. در جدول زیر مقادیر باینری و مدهای منتسب شده به کلاس‌های کاربری آورده شده است.

جدول 4-4: مقادیر باینری و مدهای منتسب شده به کلاس‌های کاربری


 

4-2 دستورکار

1) فولدری با نام test ایجاد کنید و به user و group آن حق نوشتن و اجرا را اضافه نمایید.

2) برای تمامی کاربرانِ فولدر ایجاد شده، تنها حق خواندن را ایجاد کنید.

3) برای فولدر test به user اجازه خواندن، نوشتن و اجرا و به group اجازه خواندن و اجرا و به other تنها اجازه خواندن را بدهید.

4) دستورات زیر به user، group و other چه حق دسترسی‌هایی می‌دهد؟

  • Chmod 775
  • Chmod 750
  • Chmod 644
  • Chmod 640
  • Chmod 600
  • Chmod 700

 

5-1 پیش‌آگاهی

‌‌بیش‌تر توزیع‌های لینوکس از استاندارد سلسله مراتبی فایل سیستمی (FHS-File system Hierarchy Standard) پیروی می‌کنند. این استاندارد ساختار فایل، در توزیع‌های مختلف یونیکس و لینوکس را مشابه می‌کند که برای توسعه‌های آتی مفید است. استاندارد FHS به صورت آنلاین در در سایت http://www.pathname.com/fhs/ موجود است.

در توزیع‌های مختلف لینوکس، میان فایل سیستم تفاوت‌هایی وجود دارد. برای اطلاع یافتن از سلسله مراتب فایل سیستم در توزیع، از دستور man hier استفاده می‌شود. این راهنما درباره ساختار دایرکتوری روی سیستم موجود توضیح می‌دهد. در شکل 5-1، ساختار فایل رایج سیستم‌های لینوکسی آورده شده است. همه فایل‌ها، زیرمجموعه ریشه (/) هستند. سایر فایل‌ها را می‌توان به یکی از شش دسته binary، configuration، data، in memory، usr و var طبقه‌بندی کرد. در ادامه هر یک از این دسته‌ها به طور کامل بررسی می‌شوند.

 

شکل 5-1: ساختار فایل رایج سیستم‌های لینوکسی

5-1-1 دایرکتوری ریشه (/)

ساختار فایلِ تمامی توزیع‌های لینوکس، با دایرکتوری ریشه شروع می‌شود. دایرکتوری ریشه با نماد / نشان داده می‌شود. تمامی فایل‌ها در لینوکس را می‌توان در ذیل این دایرکتوری پیدا کرد. در شکل 5-2، به محتویات موجود در دایرکتوری ریشه توجه کنید.

 

شکل 5-2: محتویات موجود در دایرکتوری ریشه

 

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

5-1-2 دایرکتوری binary

فایل‌های باینری، فایل‌هایی هستند که شامل سورس کدهای کامپایل شده (کدهای ماشین) هستند. فایل‌های باینری را می‎‌توان روی کامپیوتر اجرا کرد. چهار نوع فایل نیز در این شاخه قرار می‌گیرند که در ادامه توضیح داده می‌شوند.

5-1-2-1 /bin

دایرکتوری /bin شامل دستورات باینری برای استفاده تمامی کاربران است. با توجه به استاندارد FHS، دایرکتوری /bin باید دارای /bin/cat و /bin/date باشد. در شکل 5-3، دستورات رایج در لینوکس مانند cat و sleep و ... در دایرکتوری /bin دیده می‌شود.

فولدر /binرا می‌توان در دایرکتوری‌های دیگری نیز دید. به عنوان مثال کاربری با نام shfz را در نظر بگیرید. این کاربر می‌تواند برنامه‌های خود را در مسیر /home/shfz/bin قرار دهد.

شکل 5-3: دستورات رایج در لینوکس مانند cat و sleep و ... در دایرکتوری /bin

5-1-2-2 /sbin

شامل دستورات باینری برای تنظیمات ‌‌سیستم‌عامل است. بسیاری از دستورات باینری سیستمی نیاز به سطح دسترسی root دارند تا بتوانند وظیفه خاصی را انجام دهند. در شکل 5-4، دستورات باینری سیستمی برای تغییر آدرس IP، پارتیشن‌بندی دیسک و ایجاد یک فایل سیستم از نوع ext4 نشان داده شده است. اطلاعات کامل این دستورات که در شکل 5-4 نشان داده شده است، با استفاده از دستور ls –l اطلاعات قابل دسترسی است.

 

شکل 5-4: دستورات باینری سیستمی برای تغییر آدرس IP، پارتیشن‌بندی دیسک و ایجاد یک فایل سیستم از نوع ext4

5-1-2-3 /lib

دستورات باینری موجود در /bin و /sbin از کتابخانه‌هایی استفاده می‌کنند که در مسیر /lib قرار گرفته است. در زیر محتویات فولدر /lib نشان داده شده است.

5-1-2-4 /opt

از /opt برای ذخیره نرم‌افزارهای نصب شده (optional software) استفاده می‌شود. ‌‌بیش‌تر اوقات این نرم‌افزارها در منبع نرم‌افزارهای نصبی وجود ندارند. در بسیاری از سیستم‌ها، دایرکتوری /opt خالی است. پکیج‌های بزرگ می‌توانند تمامی فایل‌هایشان را در زیر فولدرهای /bin، /lib و /etc فولدر /opt/$packagename ذخیره کنند. به عنوان مثال اگر پکیجی shfz نام داشته باشد در دایرکتوری /opt/shfz فایل‌های نصبی خود را قرار می‌دهد و مثلا دستورات باینری خود را در /opt/shfz/bin قرار می‌دهد.

5-1-3 دایر کتوری configuration

5-1-3-1 /boot

دایرکتوری /boot شامل تمامی فایل‌هایی است که برای بوت شدن سیستم لازم است. این فایل‌ها غالبا چندان تغییر نمی‌کنند. در سیستم‌های لینوکسی معمولا دایرکتوری /boot/grub وجود دارد. این دایرکتوری شامل /boot/grub/grub.cfg است (در سیستم‌های قدیمی‌تر، این دایرکتوری شامل /boot/grub/grub.conf است) که منوی بوتی را تعریف می‌کند که قبل از شروع هسته نمایش داده می‌شود.

5-1-3-2 /etc

تمامی فایل‌های مربوط به تنظیمات خاص سیستم باید در دایرکتوری /etc قرار بگیرند. دایرکتوری /etc مخفف etcetera است. نام فایل تنظیمات غالبا مشابه نام برنامه، daemon یا پروتکل است که پسوند .conf به آن اضافه می‌شود.

5-1-4 دایرکتوری data (بعدی ها زیر این هستند؟)

5-1-4-1 /home

کاربران می‌توانند فایل‌ها و داده‌های خود را در دایرکتوری /home ذخیره کنند. معمول است که نام کاربران به صورت یک دایرکتوری در /home قرار گیرد و هر کاربر بتواند فایل‌های خود را در دایرکتوری مربوط به خود ذخیره کند. به عنوان مثال:

علاوه بر این‌که /home یک دایرکتوری برای ذخیره اطلاعات شخصی هر کاربر روی سیستم است، در آن پروفایل کاربر نیز ذخیره می‌گردد. یک پروفایل یونیکسی رایج شامل تعدادی فایل مخفی است (فایل‌هایی که اسمشان با نقطه شروع می‌شود). فایل‌های مخفی پروفایل کاربر یونیکس شامل تنظیمات خاص برای آن کاربر است.

5-1-4-2/root

در بسیاری از سیستم‌ها /root مکان پیش‌فرض برای داده‌های شخصی و پروفایل کاربر root است. اگر چنین دایرکتوری وجود ندارد administratorها باید آن را ایجاد کنند.

5-1-4-3 /srv

از دایرکتوری /srv برای داده‌هایی استفاده می‌شود که توسط سیستم شما مورد استفاده قرار می‌گیرند. طبق استاندارد FHS، داده‌های از نوع cv، rsync، ftp و www در این‌جا ذخیره می‌شوند. هم‌چنین از نام‌گذاری دایرکتوری به نام کاربر administrator نیز پشتیبانی می‌شود.

5-1-4-4/media

دایرکتوری /media به عنوان نقطه اتصال (mount point) برای اتصال دستگاه‌های قابل حمل مانند CDROM، دوربین و سایر دستگاه‌هایی که با usb به سیستم وصل می‌شوند، استفاده می‌شود. دایرکتوری /media تقریبا تازه به دنیای یونیکس وارد شده و بدون استفاده از این دایرکتوری نیز می‌توان از سیستم استفاده کرد (مثلا در Solaris9 و Solaris10). اما امروزه ‌‌بیش‌تر سیستم‌ها از این دایرکتوری برای نقطه اتصال دستگاه‌های قابل حمل استفاده می‌کنند.

5-1-4-5 /mnt

دایرکتوری /mnt باید خالی باشد و با توجه به استاندارد FHS از آن تنها برای نقاط اتصال موقتی استفاده می‌شود. کاربران admin دایرکتوری‌های زیادی را در این سمت ایجاد می‌کنند تا بتوان از آن‌ها برای سیستم فایل‌های محلی و remote مختلفی استفاده کرد.

5-1-4-6/tmp

کاربران و برنامه‌های مختلف از /tmp برای ذخیره داده‌های موقتی به هنگام لزوم استفاده می‌کنند. داده‌ای که در /tmp قرار می‌گیرد، ممکن است روی دیسک سخت یا RAM ذخیره شود. داده ذخیره شده توسط ‌‌سیستم‌عامل مدیریت می‌شود. از دایرکتوری /tmp برای ذخیره داده‌های مهم به هیچ وجه استفاده نشود.

5-1-5 دایرکتوری in memory

5-1-5-1 /dev

فایل‌های دستگاه (device files) در دایرکتوری /dev قرار می‌گیرند، اما واقعا روی دیسک سخت قرار نگرفته‌اند. فایل‌های هسته شناسایی سخت‌افزار در این دایرکتوری قرار می‌گیرند. تعدادی از دستگاه‌های فیزیکی معمول در ادامه معرفی می‌شوند.

سخت‌افزارهای رایجی همچون دیسک سخت، توسط فایل‌های دستگاه در /dev نشان داده می‌شوند. در زیر، نمونه‌ای از فایل‌های دستگاه SATA روی لپ تاپ و پارتیشن‌های IDE متصل به لپ تاپ آورده شده است.

علاوه ‌بر نمایش سخت‌افزارهای فیزیکی، برخی از فایل‌های دستگاه، خاص و بسیار کاربردی هستند. برای مثال /dev/tty1 یک ترمینال یا کنسول متصل به سیستم را نشان می‌دهد (منظور یک واسط خط فرمان است). هنگامی‌که دستورات را در خط فرمان (در واقع بخشی از واسط گرافیکی مانند Gnome یا KDE است) می‌نویسید، ترمینال شما به صورت /dev/pts/1 (عدد 1 می‌تواند تغییر کند) نمایش داده می‌شود.

از دیگر فایل‌های خاص، /dev/null است. می‌توان آن را مانند یک حفره سیاه در نظر گرفت که فضای ذخیره‌سازی نامحدودی دارد اما هیچ چیز از آن قابل بازیابی نیست. به صورت تخصصی‌تر هر چیزی که در این بخش نوشته می‌شود، نادیده (discarded) گرفته می‌شود. از این دایرکتوری زمانی استفاده شود که بخواهیم خروجی ناخواسته از دستورات، نادیده گرفته شود. تاکید می‌شود /dev/null مکان مناسبی برای ذخیره اطلاعات نیست.

5-1-5-2 /proc ارتباط با هسته

دایرکتوری /proc از جمله دایرکتوری‌های خاص است که فضایی از دیسک سخت نمی‌گیرد. در واقع دایرکتوری /proc یک چشم‌انداز از هسته است. این دایرکتوری آن‌چه که هسته مدیریت می‌کند را نشان می‌دهد و از آن برای تعامل مستقیم با هسته می‎‌توان استفاده کرد.

 

با لیست کردن محتویات دایرکتوری /proc اعداد زیاد و فایل‌های جالبی در این دایرکتوری مشاهده می‌شود.

حال بیایید در مورد ویژگی فایل‌ها در /proc جستجو کنیم. زمان و تاریخ کنونی مطابق با زمان و تاریخ هسته است که به روز بودن فایل‌ها را نشان می‌دهد.

‌‌بیش‌تر فایل‌ها در /proc حجمی ندارند اما داده دارند. برخی اوقات داده‌های زیادی هم دارند. می‌توانید این موضوع را با استفاده از دستور cat بر روی دایرکتوری /proc/cpuinfo بررسی کنید. این فایل شامل اطلاعاتی دربارهCPU است. ‌‌بیش‌تر فایل‌ها در /proc فقط خواندنی هستند و یا نیاز به سطح دسترسی root دارند. تعدادی از فایل‌ها هم قابل نوشتن هستند. تعداد زیادی فایل در /proc/sys قابل نوشتن هستند. در ادامه تعدادی از فایل‌ها در /proc مورد بررسی قرار می‌گیرند.


در معماری x86 در فایل /proc/interrupts وقفه‌های سیستم وجود دارند.

روی سیستمی با دو CPU فایل بالا به صورت زیر خواهد بود:

حافظه فیزیکی با /proc/kcore نمایش داده می‌شود. از دستور cat برای این فایل استفاده نکنید. در عوض از یک اشکال‌یاب (debugger) استفاده کنید. اندازه /proc/kcore برابر با اندازه حافظه به اضافه 4 بایت است.

5-1-5-3 /usr : Unix System Resource

اغلب، /usr مانند user تلفظ می‌شود اما توجه داشته باشید که usr مخفف Unix System Resources است. ساختار /usr داده‌هایی از نوع اشتراکی و فقط خواندنی است. برخی آن را به صورت فقط خواندنی در می‌آورند. این کار را می‌توان از روی پارتیشن خودش یا از روی یک NFS اشتراکی فقط خواندنی انجام داد. این دایرکتوری شامل دایرکتوری‌های مختلفی از جمله bin، include، lib و ... است.

5-1-5-4 /var : variable data

فایل‌هایی با اندازه غیرقابل پیش‌بینی مانند log، cache و spool در این دایرکتوری قرار می‌گیرند.

/var/log

این دایرکتوری شامل تمامی فایل‌های log است.

/var/log/message

به طور پیش فرض این فایل حاوی اطلاعات رخدادها در سیستم است و در اوبونتو با /var/log/syslog شناخته می‌شود.

/var/cache

این دایرکتوری شامل داده‌های cache مربوط به برنامه‌های متعددی است.

/var/spool

چی؟ شامل دایرکتوری‌های spool برای ایمیل و کرون (cron) است. هم‌چنین به عنوان دایرکتوری والد برای سایر فایل‌های spool نیز استفاده می‌شود (مانند فایل‌های spool چاپ).

/var/lib

این دایرکتوری شامل اطلاعات حالت و وضعیت برنامه‌ها است.

/var/…

فایل‌های ID پروسه‌ها در /var/run، فایل‌های موقت برای reboot در /var/tmp و اطلاعاتی درباره قفل فایل در /var/lock قرار دارد. نمونه‌های زیادی برای استفاده /var وجود دارد که مطالعه آن به خواننده واگذار می‌شود.


 

5-2 دستورکار

1)      آیا فایل‌های /bin/cat و /bin/echo وجود دارند؟ نوعشان چیست؟

2)      اندازه فایل هسته لینوکس (اول این فایل‌ها با vmشروع می‌شود) در /boot چقدر است؟

3)      دایرکتوری ~/test را ایجاد نمایید و دستورات زیر را اجرا کنید. حاصل این دستور چیست؟

4)      تابع random چه کاری انجام می‌دهد؟

5)      آیا می‌توانید وارد دایرکتوری ریشه شوید؟ چه فایل‌های مخفی در آنجا وجود دارد؟

6)      چرا فایل‌هایifconfig، fdisk، parted،shutdown و grub-installتنها در /sbin قرار دارند و در /bin قرار ندارند؟

7)      آیا /var/log دایرکتوری است یا فایل است؟

 

 

 

 

 


 

6-1 پیش‌آگاهی

در این آزمایش، مقدمه‌‌ای در مورد دستورات لینوکس برای دیدن اطلاعات مربوط به فرآیندها و نحوه نوشتن برنامه در این ‌‌سیستم‌عامل ذکر خواهد شد و در آزمایش‌های بعدی به نوشتن برنامه‌های سیستمی پرداخته خواهد شد.

لینوکس یک سیستم‌عامل چندوظیفه‌ای است یعنی می‌تواند همزمان چند برنامه را روی یک پردازنده اجرا کند. متناظر با هر برنامه‌ای که در سیستم اجرا می‌شود، سیستم‌عامل یک فرآیند ایجاد می‌کند. در سیستم‌عامل Linux برای هر فرآیند اطلاعات زیادی نگهداری می‌شود که در ادامه، برخی از آن‌ها شرح داده می‌شود:

PID: هر فرآیند در سیستم، یک شماره (Process ID) دارد که از این پس با نام pid به آن اشاره خواهیم کرد. سه فرآیند ویژه در سیستم‌عامل Linux تعریف شده‌اند و هنگام شروع به کارِ سیستم ایجاد می‌شوند و هنگام پایان کار سیستم از بین می‌روند. این فرآیندها شماره‌های ثابت دارند و عبارتند از coordinator با شمارة صفر، init با شمارة یک و swapper با شمارة دو.

PPID: هر فرآیند می‌تواند فرآیندهای دیگری را ایجاد کند. این فرآیند، پدر فرآیندهای جدید خواهد بود. هر یک از فرآیندهای فرزند، شمارة فرآیند پدر خود (Parent Process ID) را به همراه دارند. تمام فرآیندها در سیستم، یک پدر دارند و پدر نهایی فرآیندها، init است. پس تنها فرآیندی که پدر ندارد، init است.

6-1-1 مشاهده مشخصات فرآیندها: فرمان‌های ps و top

به منظور دیدن فرآیندهای موجود بر روی سیستم، از فرمان ps استفاده کنید. قالب کلی فرمان و نتیجة اجرای آن به صورت زیر خواهد بود:

$ ps

 

PID TT STAT TIME COMMAND

22 1 T 0:12 emacs

40 1 T 0:06 find

55 2 R 0:01 ps

قسمت‌های مختلف خروجی به ترتیب عبارتند از: شمارة فرآیند، نام پایانه‌ای که فرآیند از آن اجرا شده، وضعیت فعلی فرآیند، زمانی که فرآیند اجرا شده و نام فرآیند. اگر بعد از فرمان ps گزینة -l بکار برید، اطلاعات بیش‌‌تری راجع به فرآیندها خواهید دید.

فرمان ps فرآیندهای موجود را فقط یک بار گزارش می‌کند و سپس پایان می‌پذیرد. Linux، دارای فرمانی مشابه ps به نام top است. فرمان top پس از اجرا، ضمن نشان دادن فرآیندهای موجود، اطلاعات آن‌ها را نیز لحظه به لحظه به‌هنگام می‌کند. برای خارج شدن از فرمان top باید کلید q را فشار دهید.

6-1-2 اجرای فرآیندها در پس‌‌زمینه (background)

با افزودن علامت & به آخر هر فرمان یا برنامه، می‌‌توان آن را در پس‌‌زمینه اجرا کرد. در این حالت، Linux علاوه بر اجرای برنامة ذکر شده، با دادن اعلان خط فرمان آمادگی خود را جهت گرفتن فرمان یا برنامه‌‌ای دیگر اعلام می‌‌کند (بدون اینکه اجرای برنامة قبلی پایان یافته باشد). بدین ترتیب دو یا چند فرمان را می‌‌توان با هم اجرا کرد.

6-1-3 هستة Linux

هستة Linux وظایف مدیریت فرآیندها، مدیریت حافظه، مدیریت پرونده و ارتباط با سخت‌افزار را بر عهده دارد. هستة سیستم‌عامل کاملاً وابسته به سخت‌افزار است و باعث استقلال برنامه‌های کاربردی از سخت‌افزار می‌شود. بنابراین برنامه‌نویسی برای این قسمت با برنامه‌نویسی عادی Linux کاملاً تفاوت دارد و نیاز به داشتن آگاهی از سخت‌افزار و نیز نحوة پیاده‌سازی هسته دارد.

در Linux یک فرآیند می‌تواند با استفاده از فراخوان‌های سیستم (system call)، از امکانات هسته استفاده کند. مثلاً یک فرآیند برای باز کردن یک پرونده از فراخوان سیستم open استفاده می‌کند.

6-1-4 پوستة Linux

در Linux معمولاً برنامه‌ای از نوع پوسته، واسط ارتباطی کاربر با سیستم‌عامل می‌شود. این برنامه، فرمان کاربر را دریافت می‌کند و با استفاده از سرویس‌های قسمت‌های دیگر، آن را اجرا می‌کند. مثلاً وقتی کاربر دستور ls را برای دیدن لیست پرونده‌ها صادر می‌کند، پوسته ابتدا با استفاده از سرویس‌های سیستم پرونده، پرونده اجرایی ls را در شاخة /usr/bin می‌یابد. سپس با استفاده از یک فراخوان سیستم (یعنی فراخوان fork)، یک فرآیند جدید ایجاد کرده، در آن /usr/bin/ls را اجرا می‌کند. تفسیر و اجرای فرمان‌هایی که در پای اعلان سیستم وارد می‌گردند به عهدة پوسته است. با تعویض پوسته، قالب فرمان‌هایتان نیز ممکن است تغییر کند که برای فهمیدن این مطلب باید به راهنمای پوستة آن سیستم مراجعه کنید. پوسته‌ای که در آزمایشگاه استفاده می‌شود، bash نام دارد.

همچنین پوسته، یک زبان برنامه‌نویسی تفسیری (مشابه پرونده‌های دسته‌ا‌ی (Batch Files) در DOS) در اختیار کاربر قرار می‌دهد.

6-2 برنامه‌نویسی

در سیستم‌عامل Linux، مفاهیم متداول در مبحث سیستم‌عامل به سادگی و زیبایی پیاده‌سازی شده است. اما در ابتدا ممکن است استفاده از آن‌ها برای برنامه‌نویسان عادی اندکی نامأنوس باشد. برنامه‌نویسی در Linux را می‌توان به دو دستة کلی زیر تقسیم کرد:

- برنامه‌نویسی کاربردی (Application programming)

- برنامه‌نویسی هسته (Kernel programming)

نوع اول برنامه‌نویسی، برای ایجاد برنامه‌های عادی به کار می‌رود. مشخصة اصلی چنین برنامه‌هایی این است که مطابق معماری Linux، کاملاً مستقل از سخت‌افزار هستند. هسته به عنوان یک پل ارتباطی بین سخت‌افزار و برنامه‌ها، این استقلال را تامین می‌کند. نوع دوم برنامه‌نویسی، برای افزایش قابلیت‌های سیستم‌عامل استفاده می‌شود. مثلاً برای اینکه سیستم‌عامل بتواند از سخت‌افزار جدیدی حمایت کند، باید برنامه‌هایی از این دست را به آن افزود.

در برنامه‌نویسی برای Linux، زبان C بیش‌‌ترین کاربرد را دارد و تقریباً تمام توابعی که در برنامه‌نویسی استفاده می‌شود به زبان C نوشته شده‌اند. برای نمونه، تمام فراخوان‌های سیستم به صورت توابع زبان C ایجاد شده‌اند.

به همراه Linux، کتابخانه‌های جانبی زبان C نیز وجود دارد که در مقایسه با کتابخانه‌های معمولی امکانات بیش‌‌تری در اختیار برنامه‌نویس قرار می‌دهند و برنامه‌نویس می‌تواند در مواقعی که استفاده از فراخوان‌های سیستم مشکل می‌شود، از توابع این کتابخانه‌ها استفاده کند.

6-2-1 gcc (مترجم زبان C)

پس از آنکه برنامة خود را توسط یک ویرایشگر ایجاد کردید، باید آن را توسط برنامة gcc ترجمه کنید. قالب کلی این فرمان به صورت زیر است:

$ gcc source_filename -o executable_filename

به عنوان مثال اگر بخواهیم برنامة myfork.c را ترجمه کنیم و نام پروندة قابل اجرای آن myfok1 باشد، سطر زیر را وارد خواهیم کرد:

$ gcc myfork.c -o myfork1

6-2-2 اشکالزدایی با gdb

برای اشکال‌زدایی برنامه‌هایتان در Linux، می‌توانید از اشکال‌زدای gdb استفاده کنید. برای این کار ابتدا برنامة خود را با گزینة -g (مربوط به gcc) ترجمه کنید. سپس در پای اعلان Linux، فرمان gdb را به همراه نام پروندة قابل اجرای برنامه وارد کنید. با این کار وارد محیط gdb شده، به کمک فرمان‌های این محیط می‌توانید به اشکال‌زدایی برنامة خود بپردازید.

خلاصه‌ای از این فرمان‌ها با توضیحات مربوط در جدول 6-1 آمده است.

 

جدول 6-1: خلاصه‌ای از فرمان‌های gdb

توضیحات

شکل کلی فرمان

قرار دادن نقطة شکست (شرطی یا غیرشرطی) در خط مورد نظر

break <line#> [if condition]

اجرای برنامه

run

اجرای یک سطر از برنامه

step

اجرای یک تابع بدون اجرای سطر به سطر دستورات آن

next

نشان دادن قطعاتی از برنامه

list

اجرای برنامه تا رسیدن به خط مورد نظر

until <line#>

چاپ مقدار یک متغیر

print <var-name>

فراخوانی تابع موردنظر و نشان دادن خروجی آن

print <function>

اجرای برنامه تا رسیدن به یک دستور بازگشت به سیستم‌عامل

finish

اجرای برنامه تا رسیدن به نقطة شکست بعدی

continue

حذف نقطة شکست از خط مورد نظر

clear <line#>

حذف تمام نقاط شکست

delete

 


 

6-3 اتصال راه دور به ‌‌سیستم‌عامل لینوکس

یکی از قابلیت‌های مهم ‌‌سیستم‌عامل لینوکس، موضوع اتصال از راه دور است. برای اینکه چنین ارتباطی برقرار شود، نیاز است روی ‌‌سیستم‌عامل لینوکس برنامه ssh server نصب و فعال (active) شده باشد. برای اینکه متوجه شوید این برنامه نصب است یا خیر، کافی است در خط فرمان ‌‌سیستم‌عامل دستور زیر را وارد نمایید:

sudo service ssh status

در صورت فعال بودن، پیغامی مشابه شکل 6-1 نمایش داده خواهد شد.

 

check ssh status

شکل 6-1: نحوه آزمایش فعال بودن ssh server

 

در صورت فعال نبودن ssh server باید ابتدا برنامه مربوطه را روی ‌‌سیستم‌عامل نصب و فعال کنید:

sudo apt-get install openssh-server

sudo nano /etc/ssh/sshd_config

در فایل باز شده، دو خط مربوط به مشخص کردن شماره پورت و شماره پروتکل را از حالت کامنت خارج کنید (حذف کردن کاراکتر # در ابتدای این دو خط). سپس با فشردن کلید Ctrl+o آن را ذخیره کنید و با فشردن کلید ctrl+x خارج شوید.

برای اتصال به ‌‌سیستم‌عامل از راه دور، به یک برنامه برای شبیه‌سازی ترمینال نیاز است. یکی از معروف‌‌ترین برنامه‌هایی که برای این کار استفاده می‌شود، putty است. مثلا اگر بخواهید از ‌‌سیستم‌عامل ویندوز نصب شده روی کامپیوترتان به ‌‌سیستم‌عامل Ubuntu نصب شده روی ماشین مجازی وصل شوید، کافی است برنامه putty را از سایت putty.org دانلود کنید. سپس همان‌طورکه در شکل 6-2 نمایش داده شده است، تنظیمات را انجام دهید و روی دکمه open کلید نمایید. ترمینالی باز می‌شود و رمز عبور ‌‌سیستم‌عامل را از شما سوال خواهد کرد. در صورت صحیح بودن رمز عبور، شکل 3-6 نمایش داده خواهد شد که همان ترمینال لینوکس خواهد بود و تمام فرامین را می‌توانید در این بخش وارد کنید.

شکل 6-2: اتصال به ‌‌سیستم‌عامل لینوکس با استفاده از نرم‌افزار putty

 

Image result for putty ubuntu

شکل 6-3: اتصال موفقیت‌آمیز به ‌‌سیستم‌عامل لینوکس با استفاده از نرم‌افزار putty

 

6-4 دستورکار

1) فرمان ps را اجرا کنید. خروجی این فرمان را ببینید و دربارة اطلاعاتی که توسط این فرمان در مورد فرآیندها نشان داده می‌شود را توضیح دهید.

2) ویرایشگر vi را در پس زمینه اجرا کنید (vi & ). فرمان ps را اجرا کنید. نتایج اجرای این فرمان با نتایج مشاهده شده در گام قبلی چه تفاوتی دارد؟ توضیح دهید.

3) فرمان top را وارد کنید و در مورد آنچه که بر روی صفحه می‌بینید توضیح دهید.

4) وارد ویرایشگر nano شوید. یک برنامة کوچک C بنویسید تا پیغام‌هایی را بر روی صفحة نمایش چاپ کند. آن را در پرونده‌ای با نام first.c ذخیره کنید.

5) برنامة first.c را ترجمه و اجرا کنید (در صورت اجرای برنامه gcc و صحیح بودن برنامه، فایلی اجرایی با نام first.o ایجاد خواهد شد که برای اجرای آن باید دستور ./first.o را در خط فرمان وارد نمایید).

6) برنامه فوق را ویرایش کنید و یک حلقه بی‌پایان در انتهای برنامه قرار دهید. سپس برنامه را کامپایل کنید و سه بار برنامه را در پس زمینه اجرا کنید (با استفاده از &، برنامه را در پس زمینه اجرا کنید).

7) با استفاده از دستور top، شماره PID فرآیندهای مربوط به سه بار اجرای برنامه فوق را بدست آورید. سپس با استفاده از دستور kill فرآیندهای فوق را از حافظه خارج کنید.

8) با استفاده از دستور ifconfig در خط فرمان، آدرس IP ‌‌سیستم‌عامل نصب شده روی virtual machine را به دست آورید. سپس طبق توضیحات انجام شده در بخش 6-3، از ‌‌سیستم‌عامل ویندوز به ‌‌سیستم‌عامل لینوکس نصب شده در virtual machine متصل شوید. اکنون برنامه first.o که قبلا ایجاد کرده بودید را اجرا کنید.


 

7-1 پیش‌آگاهی

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

7-1-1 حالات مختلف فرآیند (Process)

همان‌طورکه پیش‌‌تر دیدید، دستور ps می‌تواند وضعیت فرآیندها را نمایش دهد. یکی از مشخصه‌هایی که al- ps برای یک فرآیند نشان می‌دهد، STAT نام دارد که حالت فرآیند را به صورت یک کُدِ حداکثر سه حرفی نشان می‌دهد.

حرف اول این کُد می‌تواند یک از حروف زیر باشد:

R: برای فرآیندهای در حال اجرا

S: برای فرآیندهای منتظر (Waited)

Z: برای فرآیندهای جادویی (Zombie)

T: برای فرآیندهایی که متوقف (Stopped) یا ردگیری (Trace) شده‌اند (یا به اصطلاح آمادة اجرا هستند).

D: برای فرآیندهایی که در حالت انتظار بدون انقطاع یا انتظار درحافظة جانبی هستند.

اگر حرف دوم این کُد W باشد، نشان‌دهنده آن است که فرآیند مورد نظر هیچ صفحه‌ای در حافظة اصلی ندارد و به‌طور کامل مبادله (Swap) شده است.

حرف سوم، مربوط به زمان‌بندی و اولویت فرآیندها است. اگر این حرف N باشد، نشان‌دهنده این است که ارزش مطلوب (Nice Value) فرآیند، بزرگ‌‌تر از صفر است (این نوع فرآیندها به وقت کم‌‌تری از پردازنده نیاز داشته و اولویت بیش‌‌تری دارند).

در سیستم‌عامل Linux یک فرآیند می‌تواند نُه حالت مختلف داشته باشد. حالات مختلف فرآیندها در جدول 71نشان داده شده است. وقتی فرآیند پدر با فراخوان سیستم fork، فرآیند جدیدی ایجاد می‌کند، فرآیند ایجاد شده بسته به مقدار حافظة آزاد سیستم، وارد یکی از مراحل 3 یا 5 خواهد شد. برای سادگی، فرض کنید فرآیند وارد مرحلة 3 می‌گردد. وقتی زمان‌بند فرآیندها، به طور تصادفی آن را برای اجرا برگزید، فرآیند وارد مرحلة اجرا در فضای هسته می‌شود و در این هنگام کار فراخوانِ سیستم fork به اتمام می‌رسد. پس از این مرحله ممکن است فرآیند به مرحلة 1 برود.

بعد از یک برش زمانی، به علت وقوع وقفة ساعت سیستم، فرآیند به مرحلة 2 باز می‌گردد (چون تمام فرآیندها این وقفه را دریافت می‌کنند). هسته، از این وقفة ساعت برای زمان‌بندی و اولویت‌بندی فرآیندها استفاده می‌کند. پس از زمان‌بندی، ممکن است فرآیند دیگری برای اجرا انتخاب گردد. در این هنگام، فرآیند قبلی وارد حالت 7 می‌گردد و منتظر می‌ماند تا برای اجرا دوباره انتخاب گردد. اگر فرآیند در حال اجرا در فضای کاربر، بخواهد از سرویس‌های هسته استفاده کند، وارد مرحلة 2 می‌گردد. اگر این سرویس، کاری مانند ورودی/خروجی باشد، فرآیند وارد مرحلة 4 می‌شود و خود را متوقف می‌سازد. این توقف تا زمانی ادامه می‌یابد که به ‌واسطة اتمام کارِ ورودی/خروجی، وقفه‌ای به پردازنده برسد و سرویس روتین وقفه، فرآیند مربوطه را بیدار کند. این کار باعث می‌شود تا فرآیند به مرحلة 3 بازگردد و منتظر شود تا هسته آن را زمان‌بندی کند.

 

جدول 7-1: حالات مختلف فرآیندها

شماره

نام

شرح

1

اجرا در فضای کاربر

فرآیند، موقع اجرای عملیات عادی خود در این حالت قرار دارد.

2

اجرا در فضای هسته

فرآیند، هنگام استفاده از سرویس‌های هسته در این حالت قرار دارد. مانند اجرای فراخوان‌های سیستم و روال خدماتی وقفه‌ها.

3

آماده به اجرا در حافظة اصلی

فرآیند در حال اجرا نیست، ولی در حافظه اصلی قرار دارد و منتظر دریافت وقت پردازنده است تا اجرا شود.

4

متوقف (Sleep) در حافظة اصلی

فرآیند در حافظة اصلی است و منتظر پایان یافتن عملی مانند ورودی/خروجی است تا دنبالة اجرایش را از سر بگیرد.

5

آماده به اجرا در حافظة جانبی

فرآیند آمادة اجرا است، ولی به علت کمبود حافظه، به حافظة جنبی منتقل شده است و قبل از آن که وقت پردازنده به آن تخصیص داده شود، باید به حافظة اصلی منتقل گردد.

6

متوقف در حافظة جانبی

مشابه حالت 4 است، با این تفاوت که فرآیند به علت کمبود حافظه، توسط هسته به حافظة جانبی منتقل شده است تا حافظة کافی برای فرآیندهای در حال اجرا فراهم گردد.

7

قبضه شده (Preempted)

فرآیند از حالت اجرا در فضای کاربر به حالت اجرا در فضای هسته منتقل می‌گردد. ولی در این هنگام زمان اختصاص یافته به آن پایان می‌یابد و زمان‌بند فرآیندها، فرآیند دیگری را برای اجرا انتخاب می‌کند.

8

ایجاد

هر فرآیندی که ایجاد می‌شود، نخست در این حالت قرار می‌گیرد. در این حالت فرآیند نه در حال اجرا است و نه در حال توقف. این حالت نقطة شروع تمامی ‌فرآیندها (به جز فرآیند با pid صفر) است.

9

جادویی (Zombie)

هر فرآیند توسط تابعexit، با به ‌جا گذاشتن اطلاعاتی برای پدر خویش (فرآیندی که این فرآیند را اجرا کرده است)، به کار خویش خاتمه می‌دهد و در حالت جادویی قرار می‌گیرد. هسته، تمام منابع به جز کُد خروج فرزند را از این نوع فرآیندها می‌گیرد. این منابع شامل حافظه، پردازنده و . . . است. کُد خروج فرزند تا زمان فراخوانی wait یا waitpid توسط پدر معتبر است. این فراخوانی‌ها باعث می‌شوند پدر از وضعیت ِ(کُد) خروج فرزندش مطلع شود. اگر پدر منتظر دریافت کُد خروج فرزندش نشود و کارش را به‌پایان برساند، درحالی‌که هنوز حیات فرزند ادامه دارد، فرزند بی‌سرپرست (Orphan) می‌شود. مدیریت این نوع فرآیندها به عهدة هستة سیستم‌عامل است.

اگر فضای کافی برای فرآیندهای در حال اجرا، در حافظه موجود نباشد، فرآیند مبادله‌کننده (swapper) (با شمارة صفر) بعضی از فرآیندها را بر روی دیسک منتقل می‌کند و به این ترتیب فرآیند وارد مرحلة 5 می‌گردد. همین اتفاق ممکن است برای فرآیندهای متوقف در حافظه (حالت 4) نیز اتفاق بیفتد. وقتی کار فرآیند به پایان رسید، وارد مرحلة 9 می‌شود.

7-1-2 ایجاد فرآیند جدید

برای ایجاد یک فرآیند جدید، از یک فراخوان سیستم به نام fork استفاده می‌شود. خروجی fork از جنس pid_t (درحقیقت، یک عدد صحیح) است. این خروجی همان شناسة فرآیند (شمارة فرآیند ‏PID) است.

با احضار فراخوان سیستم fork، فرآیند جدیدی ایجاد می‌شود که پدر آن، فرآیندی است که fork را فراخوانده است. فرآیند جدید در کنار بقیة فرآیندهای موجود در سیستم قرار می‌گیرد و منتظر می‌شود تا وقت پردازنده به او اختصاص پیدا کند. کُد اجرایی این فرآیند دقیقاً همانند پدرش است و همان چیزی را اجرا می‌کند که پدرش اجرا می‌کرده است. فرآیند فرزند، محیط، متغیرها، پرونده‌های باز، شناسه‌های پروندة پدر را به ارث می‌برد. البته تفاوت‌های اندکی نیز بین این دو فرآیند وجود دارد از جمله شمارة فرآیند (PID) و شمارة فرآیند پدر (PPID).

قطعه کُد زیر را در نظر بگیرید:

pid_t child_id;

child_id = fork();

printf("forked\n");

با اجرای این دستور، یک فرآیند جدید ایجاد خواهد شد که دارای کُدی به صورت فوق بوده، شمارندة برنامة (Program counter) آن، به دستورالعمل بعد از fork اشاره می‌کند. اکنون دو فرآیند وجود دارند که دستورالعمل بعدی را اجرا خواهند کرد و در نتیجه، کلمة forked دو بار چاپ خواهد شد.

تذکّر: pid_t یکی از انواع تایپ‌های صحیح زبان C تحت Linux است که محتوای آن می‌تواند شمارة یک فرآیند باشد. شمارة فرآیند (PID)، مشخّصه‌ای است که سیستم‌عامل توسط آن یک فرآیند را می‌شناسد و به آن دسترسی پیدا می‌کند.

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

7-1-3 اجرای عملیاتی متفاوت با عملیات فرآیند اصلی در فرآیند ایجاد شده

توابع exec: فراخوان‌های exec مجموعه توابع مفیدی هستند که اجرای یک دستور سیستم‌عامل را (که قاعدتاً بایستی از خط فرمان اجرا شود) در داخل برنامه امکان‌پذیر می‌سازند. برای اطلاعات بیش‌‌تر در مورد این توابع به کتاب‌های آموزش زبان C مراجعه کنید. اجرای exec شبیه فراخوانی یک روال معمولی است و پس از اتمام کار، به بعد از محل فراخوانی exec برمی‌گردیم. اما در Linux، تمام شدن اجرای exec به اجرای برنامه (که شامل تابع main و فراخوانی exec است) خاتمه خواهد داد.

در اینجا به نظر می‌رسد که اگر بتوان تابع exec را در مسیری جدا از مسیر برنامة اصلی اجرا کرد، مشکل قابل حل خواهد بود. اگر برای ایجاد این مسیر جدید، بعد از fork یک فراخوانی exec قرار دهیم (مطابق شکل 71)، این تابع در فرآیند پدر نیز اجرا خواهد شد که مورد نظر ما نیست (مانند آنچه در مورد printf در قطعه کُد بالا داشتیم).

برای رفع مشکل ذکر شده، راه‌حلی به شرح زیر ارائه می‌شود:

راه‌حل: عبارت()child_id = fork را در نظر بگیرید:

اگر عملیات ایجاد فرآیند با موفقیت انجام شود و فرآیند فرزند ایجاد شود، متغیر child_id که در دو فرآیند پدر و فرزند وجود دارد، در هر دوی آن‌ها مقدار خواهند گرفت. در فرآیند پدر مقداری بزرگ‌‌تر از صفر (به‌ عنوان شمارة فرآیند فرزندِ ایجاد شده) در child_id خواهدگرفت و در فرآیند فرزند، مقدار صفر در این متغیر ذخیره خواهد شد.

اگر اشکالی در ایجاد فرآیند فرزند به وجود آید، fork مقدار 1- را برگشت خواهد داد که در متغیر child_id از فرآیند پدر ذخیره خواهد شد (فرآیند فرزند به‌وجود نیامده و بنابراین متغیری متناظری هم نخواهد داشت).


شکل 71: وضعیت فرآیند اصلی (پدر) و فرآیند ایجاد شده (فرزند) بلافاصله بعد از fork

 

با توجه به مطالب بالا می‌توان بعد از fork قسمت‌های اختصاصی پدر و فرزند را مشخص کرد.

قطعه کُد شکل 7-2 را در نظر بگیرید. قسمت‌هایی که مقدار برگشتی از fork، 1- نبوده است، می‌توانند به عنوان قسمت اشتراکی بین هر دو فرآیند در نظر گرفته شوند.

 

شکل 7-2: مشخص کردن قسمت‌های اختصاصی فرآیندهای پدر و فرزند

child_id = fork();

if (child_id == -1)

{

/*معمولا در این حالت تابع با یک کُد خطا ترک می‌شود. خطا در ایجاد فرآیند فرزند: */

}

if (child_id == 0)

{

/* قسمت اختصاصی فرزند:

کارهای مربوط به فرآیند فرزند را در این قسمت می‌توان انجام داد. این قسمت با اینکه

درفرآیند پدر نیز وجود دارد، هرگز در فرآیند پدر اجرا نمی‌شود. به این علت که درصورت

در فرآیند پدر مقدار بزرگ‌‌تر از صفر به خود می‌گیرد. child_id، forkموفقیت

*/}

else

{

/* قسمت اختصاصی پدر:

کارهای اختصاصی فرآیند پدر نیز در این قسمت انجام می‌گیرد. زیرا در صورت

در فرآیند فرزند مخالف صفر نخواهد شد child_id، forkموفقیت

*/

}

 

البته مانند شکل 7-2 می‌توان قسمت فرآیندهای پدر و فرزند را از هم جدا کرد. کد شکل 7-2 را می‌توان به صورت زیر هم نوشت:

 

child_pid=fork();

switch(child_pid)

{

case -1: /* خطا در ایجاد فرآیند */

case 0: /* فرآیند فرزند */

/* execute one file */

default: /* فرآیند پدر */

}

 

7-1-4 متوقف کردن یک فرآیند

فرض کنید که یک فرآیند، با استفاده از fork، فرآیند جدیدی را به وجود آورده است. به محض تولید فرآیند فرزند، این دو فرآیند به موازات هم اجرا خواهند شد. ولی گاهی این عمل مورد نظر نیست بلکه فرآیند پدر باید منتطر فرزند بماند تا کار او تمام شود، آنگاه به کار خود ادامه دهد. برای این کار، فرآیند پدر باید از فراخوان سیستمِwait استفاده کند. این تابع به صورت زیر تعریف شده است:

int wait(int* status)

فرآیندی که این تابع را احضار کرده باشد، تا اتمام کار یکی از فرزندانش صبر خواهد کرد (مقدار خروجی این تابع همان PID فرآیند فرزندی است که کارش تمام شده‌ است و کد خروج این فرزند در پارامتر status ظاهر خواهد شد). اگر فرآیند احضارکننده هیچ فرزندی نداشته باشد، این تابع بلافاصله بازگشت می‌کند.

فراخوان سودمند دیگر برای متوقف کردن یک فرآیند،waitpid است. در مورد آن می‌توان از صفحات راهنمای موجود درLinux کمک گرفت. این تابع، فرآیند جاری را قادر می‌سازد تا منتظر اتمام کار فرآیند فرزندی شود که شمارة آن فرآیند فرزند را یه عنوان پارامتر به تابع waitpid ارائه کرده است.

 

7-1-5 راهنمایی‌هایی برای انجام آزمایش

همان‌طورکه در پیش‌آگاهی ملاحظه کردید، توابع exec پس از انجام کار خود، در صورت موفقیت، خاتمه یافته و باعث برگشت به برنامه صدا کننده نمی‌شوند. در طی انجام این آزمایش به دنبال راه حلی برای استفاده مناسب از این توابع هستیم.

تابع fork_cmd مطابق تعریف به صورت زیر:

pid_t fork_cmd(char* cmd, char* argv; ([]

در پرونده fork.c تعریف شده است. شما می‌توانید با تکمیل این تابع شرایطی را فراهم کنید که دستور ارسالی در پارامتر cmd با پارامترهای ارسالی احتمالی در argv اجرا شود. مثلاً با صدا کردن تابع زیر:

fork_cmd("/usr/bin/ls","-al");

می‌توان اجرای دستور ls -al را مشاهده کرد. برای امتحان درستی fork_cmd، پروندة fcd.c را با توجه به توضیحات آن، تکمیل کنید.

در قسمت بعد و در ادامه fork_cmd در پرونده‌ای با نام fork.c، یک تابع fork_proc بنویسید که عین عملیات fork_cmd را برای انجام یک تابع proc انجام دهد. برای امتحان درستی fork_ proc، پروندة fpd.c را با توجه به توضیحات آن تکمیل کنید. (دستور کار؟)

تذکر: مقادیر خروجی (مقادیر برگشتی به فرآیند پدر) برایfcd.c و fpd.c، در صورت موفقیت، شمارة PID فرزند خواهد بود و در صورت عدم موفقیت به شرح زیر خواهد بود:

1 : اشکال در اجرای fcd.c یا fpd.c

0 : اجرای صحیح fcd.c یا fpd.c

 

7-2 دستورکار

1) عملکرد تابع getpid را از صفحات راهنما به وسیلة دستور man بیابید. با استفاده از این دستور در پرونده pid.c برنامه‌ای بنویسید که شمارة PID خود را چاپ کند. این برنامه را با استفاده از gcc ترجمه و به دفعات اجرا کنید. چه نتیجه‌ای می‌گیرید؟

2) پروندة fork.c را باز کرده، تابع fork_cmd را تکمیل کنید (با توجه به راهنمایی موجود در پیش‌آگاهی) .

3) با فراخوانی تابع fork_cmd در پرونده fcd.c، صحت قسمت قبل را بررسی کنید. چنانچه در نوشتن fork_cmd اشکالی نداشته باشید، پس از اجرای fcd بایستی خروجی فرمان ps –al را ببینید. خروجی برنامه را توجیه کنید.

4) با استفاده از تابع wait، خروجی برنامهfcd را اصلاح کنید ( به مربی نشان دهید).

5) در پرونده fork.c، تابع fork_proc را تکمیل کنید. با فراخوانی تابع fork_proc در پرونده fpd.c صحت قسمت قبل را بررسی کنید ( به مربی نشان دهید).

6) برنامهsystem_derive.c را چنان تکمیل کنید که بتوان با استفاده از تابع system دستور ps –a را اجرا کرد. ‎آیا تابع system از fork استفاده می‌کند؟ چرا؟


 

8-1 پیش‌آگاهی

هدف از این آزمایش آشنایی با مفهوم نخ و نحوه برنامه نویسی چند نخی در ‌‌سیستم‌عامل لینوکس است.

8-1-1 مفهوم چند وظیفه‌‌ای و چندنخی

توانایی یک سیستم‌عامل در اجرای چندبرنامه به طور موازی، چندوظیفه‌ای نام دارد. در واقع، سیستم‌عامل از ساعت سخت‌افزاری برای اختصاص برهه‌های زمانی (Time slice) به فرآیندهایی که به طور همزمان اجرا می‌شوند، استفاده می‌کند. اگر این برهه‌های زمانی به حد کافی کوچک باشند و تعداد کارهایی که می‌خواهیم همزمان انجام شوند، زیاد نباشد، به نظر می‌رسد که کارها به طور همزمان اجرا می‌شوند.

چندوظیفه‌ای چیز تازه‌ای نیست. چندوظیفه‌ای حتی در Mainframeها نیز وجود دارد. MainFrameها این توانایی را دارند که صدها ترمینال به آن‌ها متصل شوند و کاربر هر ترمینال احساس می‌کند که ماشین منحصراً در اختیار او است. به علاوه، سیستم‌عامل MainFrameها اغلب به کاربران این امکان را می‌دهد که کارهایی را به زمینه (Background) بفرستند. این کارها زمانی انجام می‌شوند که کاربر می‌تواند روی چیزهای دیگر کار کند.

چندنخی قابلیتی است که به یک برنامه اجازه می‌دهد درون خودش، چندوظیفه‌ای شود. برنامه می‌تواند به چند نخ اجرای جداگانه که به طور همزمان اجرا می‌شوند، شکسته شود. در ابتدا این امر چندان مهم جلوه نمی‌کند. ولی این برنامه‌ها می‌توانند از چندنخی برای اجرای کارهای طولانی در زمینه استفاده کنند، بدون آنکه کاربر مجبور باشد مدت زیادی کار با ماشین را رها کند. در محیط‌های چندنخی، برنامه‌ها می‌توانند به تکه‌هایی (که نخ اجرا نام دارند) شکسته شوند که همزمان اجرا شوند.

در پیاده‌سازی، یک نخ با یک تابع نشان داده می‌شود که ممکن است توابع دیگری را فراخوانی کند. اجرای یک برنامه با نخ اصلی (اولیه)‌اش شروع می‌شود که در برنامه‌های سنتی C، main نام دارد. در حین اجرا، برنامه می‌تواند با مشخص کردن نام یک تابع در یک فراخوانی سیستم، نخ جدیدی ایجاد کند. سیستم‌عامل به همان طریقی که بین فرآیندها رفت و آمد می‌کند، به طور نوبه‌ای بین نخ‌ها رفت و آمد می‌کند.

8-1-2 امکانات لینوکس برای کار با نخ‌ها

در لینوکس توابع مربوط به نخ‌ها در کتابخانه pthread.h تعریف شده‌اند. این توابع جزء توابع کتابخانه‌‌ای استاندارد C نیستند. بنابراین هنگام استفاده و لینک کردن فایل‌های object باید از پارامتر lpthread- استفاده شود. به عنوان مثال اگر نام برنامه‌‌ای که در آن از کتابخانه نخ‌ها استفاده شده است prog1.c باشد، آنگاه برای کامپایل و اجرای آن باید دستورات زیر را استفاده کرد:

gcc –c prog1.c

gcc –o prog1 prog1.o –lpthread

./prog1

از تابع pthread_create برای ایجاد نخ استفاده می‌شود که پارامترهای آن به صورت زیر است:

pthread_create(& th_id, NULL, &function_name, NULL);

توضیحات مربوط به پارامترهای این تابع در ادامه آمده است.

پارامتر اول، th_id، شماره شناسایی thread ایجاد شده است. این شماره شناسایی، توسط تابع pthread_create ایجاد و به این متغیر انتساب داده می‌شود. این متغیر باید در برنامه به صورت زیر از نوع pthread_t تعریف شود:

pthread_t th_id;

پارامتر دوم که در این مثال با NULL مقداردهی شده است، یک استراکچر است که حاوی ویژگی‌هایی است که نخ طبق آن ایجاد می‌شود. برخی از این ویژگی‌ها عبارتند از: اندازه پشته، اولویت، سیاست زمان‌بندی. در صورتی‌که این پارامتر NULL گذاشته شود به این مفهوم است که باید از مقادیر پیش‌فرض استفاده شود.

پارامتر سوم: نام تابعی است که قرار است در این نخ اجرا شود. هر نخ وقتی ایجاد شد باید شروع به اجرای کد تخصیص داده شده به آن نماید. نحوه تعریف این تابع به صورت زیر است:

void * function_name(void *param)

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

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

struct param {

char str[255];

int count;

}

پارامتر چهارم که در این مثال با NULL مقداردهی شده است، اشاره‌گر به پارامترهایی است که قرار است برای تابع نخ ارسال گردد. اگر این پارامتر NULL باشد به مفهوم این است که تابع نخ نیاز به پارامتری ندارد.

8-2 دستورکار

1-برنامه زیر را با استفاده از ویرایشگر nano وارد و سپس کامپایل و اجرا کنید. خروجی برنامه را توجیه کنید.

#include<pthread.h>

#include<stdio.h>

void *printx(void * th_param)

{

while(1)

printf("x");

return NULL;

}

main()

}

int i;

pthread_t th_id;

pthread_create(&th_id,NULL,&printx,NULL);

while(1)

printf("0”);

return 0;

{

2-تابعی به نام printy، بنویسید که کاراکتر y را در یک حلقه بینهایت چاپ کند. سپس برنامه سوال1 را به نحوی تغییر دهید که دو نخ ایجاد شود، یکی تابع printx و دیگری تابع printy را اجرا کند. سپس برنامه را کامپایل و اجرا کنید. خروجی برنامه را توجیه نمایید.

3-در برنامه قبلی، حلقه بینهایت که در تابع main وجود دارد را محدود کنید(مثلا حلقه 100000 بار اجرا شود) برنامه را کامپایل و اجرا کنید. با توجه به مشاهدات خود از اجرای برنامه چه نتیجه ای می گیرید؟

4-در مثالهای قبل تابعی که به نخ انتساب داده می شد پارامتر نداشت. حال می خواهیم به جای توابع printx و printy که در سوال 2 نوشته شد تابعی به نام print_char بنویسید که کاراکتری را به عنوان آرگومان دریافت و آن را چاپ کند. در برنامه اصلی با استفاده از دستورات زیر دو نخ ایجاد شوند که کاراکترهای a و b را چاپ نمایند.

char th_arg1=’a’;

char th_arg1=’b’;

 

pthread_create(&th_id1,NULL,&print_char,&th_arg1);

pthread_create(&th_id1,NULL,&print_char,&th_arg2);

 

راهنمایی: در تابع print_char با توجه به اینکه به صورت پیشفرض، پارامتر ورودی از نوع void * است باید با استفاده از دستور زیر، آن را به نوع char * تغییر دهید سپس با استفاده از دستور printf(“%c”,*ch) آنرا چاپ کنید.

char *ch=(char *)th_param;

5-در برنامه قبل، پارامتر ارسال شده برای تابع نخ، فقط یک کاراکتر بود، حال می خواهیم برنامه سوال 4 را به گونه ای تغییر دهیم که علاوه بر کاراکتر، تعداد دفعات چاپ آن نیز ارسال گردد(در واقع دو پارامتر برای تابع ارسال گردد) با توجه به توضیحات داده شده در بخش پیش گزارش، با تعریف استراکچری به صورت زیر، برنامه سوال 4 را به گونه ای بازنویسی کنید که در تابع print_char کاراکتر دریافت شده را به تعداد دفعات ذکر شده در پارامتر چاپ کند.

struct th_param {

char ch;

int count;

}

نحوه فراخوانی در تابع اصلی:

struct th_param th_arg1, th_arg2;

th_arg1.ch=’a’;

th_arg1.count=100000;

th_arg2.ch=’b’;

th_arg2.count=300000;

pthread_create(&th_id1,NULL,&print_char,&th_arg1);

pthread_create(&th_id1,NULL,&print_char,&th_arg2);

 

 


 

9-1 پیش آگاهی

در آزمایش 8، با مفهوم نخ و نحوه ایجاد آن آشنا شدید. در این آزمایش نحوه هماهنگ سازی نخهای متعدد را خواهید دید و نحوه استفاده از سمافور برای هماهنگ کردن نخهای متعدد را آزمایش خواهید کرد.

9-1-1 توابع مربوط به هماهنگی نخ‌ها

در این بخش، توابع مربوط به هماهنگی نخها، معرفی و نحوه استفاده از آنها آورده می شود.

تابع join

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

#include <pthread.h>

int pthread_join(pthread_t thread, void **value_ptr);

در حالت ساده شده، مقدار پارامتر دوم را می توان NULL گذاشت.

سمافور mutex

Mutex یک سمافور باینری است که برای مدیریت همزمانی دسترسی به ناحیه بحرانی استفاده می شود. برای استفاده از این سمافور ، باید ابتدا یک متغیر از نوع mutex به صورت زیر تعریف شود(با توجه به اینکه این متغیر، یک متغیر سراسری باید باشد که توسط توابع نخ و برنامه اصلی قابل مشاهده باشد بنابراین تعریف آن باید در ابتدای برنامه باشد(بلافاصله بعد از #include های برنامه))

pthread_mutex_t my_mutex_name : PTHREAD_MUTEX_INITIALIZER;

در دستور فوق، نامی که برای mutex انتخاب شده است my_mutex_name است.

بعد از اینکه متغیر تعریف شد، با استفاده از دو دستور زیر میتوان متغیر mutex را قفل یا آزاد کرد

pthread_mutex_lock(&my_mutex_name)

pthread_mutex_unlock(&my_mutex_name)


 

9-2 دستورکار

1-تابع main، سوال آخر از آزمایش 8 را به صورت زیر بنویسید

main()

}

int i;

pthread_t th_id1, th_id2;

struct th_param th_arg1, th_arg2;

th_arg1.ch=’a’;

th_arg1.count=100000;

th_arg2.ch=’b’;

th_arg2.count=100000;

pthread_create(&th_id1,NULL,&print_char,&th_arg1);

pthread_create(&th_id1,NULL,&print_char,&th_arg2);

printf(“\nTwo Threads are generated”);

pthread_join((&th_id1,NULL);

pthread_join((&th_id2,NULL);

printf(“\nThreads have been finished”);

{

الف) برنامه را اجرا کنید و مشاهدات خود را بنویسید.

ب) اگر در برنامه فوق، دو دستور pthread_join را حذف کنید چه اتفاقی می افتد؟

ج) اگر در برنامه فوق، جای دو دستور pthread_join را جابجا کنید چه اتفاقی می افتد؟

د) اگر دو دستور join را حذف کنید و قطعه کد زیر را جایگزین کنید، نتیجه اجرا چه خواهد شد؟ توجیه کنید

While1(1)

}

Printf(“1”);

pthread_join((&th_id1,NULL);

Printf(“2”);

pthread_join((&th_id2,NULL);

Printf(“3”);

}

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

و) روشی ارائه دهید که در انتهای برنامه main، پیغامی چاپ کند که نشان دهد کدام یک از دو نخ زودتر به پایان رسیده اند.

2-برنامه زیر را در نظر بگیرید. همانگونه که ملاحظه می کنید ترتیب چاپ شدن x و y ها تحت کنترل برنامه نویس نیست و کاملا وابسته به نحوه زمانبندی CPU توسط سیستم عامل است. در این بخش قصد داریم با استفاده از mutex ترتیب چاپ شدن را تحت کنترل برنامه نویس در بیاوریم.

main()

}

int i;

pthread_t th_id1,th_id2;

pthread_create(&th_id1,NULL,&printx,NULL);

pthread_create(&th_id2,NULL,&printy,NULL);

pthread_join((&th_id1,NULL);

pthread_join((&th_id2,NULL);

return 0;

{

 

#include<pthread.h>

#include<stdio.h>

void *printx(void * th_param)

{

for(int i=0; i<100; ++i)

printf("x");

return NULL;

}

void *printy(void * th_param)

{

for(int i=0; i<100; ++i)

printf("y");

return NULL; }

 

الف) چگونه با تعریف دو mutex میتوان برنامه را به نحوی تغییر داد که چاپ x و y ها به این صورت باشد: xyxyxyxyxy……..

ب) برنامه را به گونه ای تغییر دهید که خروجی زیر را تولید کند.

50تا

50تا

50تا

50تا

xx…x

yy…y

xx…x

yy…y

3-برنامه سوال 2 را با تغییرات زیر به صورت مساله کلاسیک تولید کننده- مصرف کننده تبدیل نمایید.

تابع printx را به produce_y تغییر نام دهید. سپس یک آرایه 100 عنصری از نوع کاراکتر تعریف و آنرا با ‘0’ مقدار دهی نمایید. دو متغیر به نامهای front و tail از نوع صحیح تعریف نمایید(برای پیاده سازی یک بافر حلقوی). تابع produce_y، در یک حلقه، با ایجاد یک تاخیر بین 1 تا 10 ثانیه، یک کاراکتر در بافر می نویسد و این کار را در یک حلقه 1000 تایی تکرار می کند و تابع printy، دائما در حال چک کردن بافر حلقوی است به محض دریافت یک کاراکتر آن را چاپ می کند.

برای ایجاد تاخیر از کتابخانه و تابع زیر استفاده کنید:

#include <unistd.h>

int usleep(useconds_t usec);

برای تولید عدد تصادفی از کتابخانه و تابع زیر استفاده کنید:

#include <stdlib.h>

int rand(void);//یک عدد صحیح بین صفر تا 32768 برمیگرداند

4-یک نخ جدید به برنامه سوال 3 اضافه کنید. کار این نخ به این صورت باشد که اگر صف خالی بود، کل بافر را با صفر پر کند سپس مادامی که صف خالی است، کاراکتر ‘0’ را با فاصله زمانی یک ثانیه به صورت مرتب چاپ کند.

 

 

 


 

10-1 پیش‌آگاهی

هدف از این آزمایش، آشنایی با ارتباط فرآیندها با لوله (Pipe) و دوطرفه‌کردن (Dupآشنایی با سیگنال‌ها و چگونگی فراخوانی آن‌ها، همزمان کردن فرآیندها با استفاده از سیگنال‌ها و پیاده‌سازی یک الگوریتم موازی با استفاده از فرآیندها و سیگنال‌ها است.

10-1-1 لوله (Pipe)

 

شکل 10-1: نحوة عملکرد لوله


ج)

الف)


یکی از روش‌های انتقال اطلاعات بین فرآیندها، لوله (Pipe) است. یک فرآیند، اطلاعات را در یک سوی لوله می‌نویسد و فرآیند دیگر آن‌ها را از سوی دیگر لوله می‌خواند. لوله از دو پرونده تشکیل شده است که یکی از این پرونده‌ها فقط خواندنی و دیگری فقط نوشتنی است. بنابراین فرآیندی که می‌خواهد اطلاعات را ارسال کند، باید در پروندة فقط نوشتنی بنویسد تا فرآیند دریافت‌کنندة اطلاعات، آن‌ها را از پروندة فقط خواندنی بخواند. خط‌لوله معمولاً بین فرآیند پدر با فرزندش برقرار می‌شود. نحوة عملکرد لوله را با استفاده از شکل 10-1 بهتر می‌توان درک کرد. همان‌طورکه در شکل 10-1-ب نشان داده شده است، اطلاعاتی که از یک لوله عبور می‌کند، از داخل هسته می‌گذرد.

ب)

برای ایجاد لوله از فراخوان سیستم با نام pipe استفاده می‌شود. این فراخوان سیستم آدرس یک آرایة دوتایی از جنس int را دریافت می‌کند و در صورت موفقیت، دو شناسة پرونده در آن‌ها قرار می‌دهد. شناسة اولین پرونده، فقط خواندنی و شناسة دومی، فقط نوشتنی است.

اگر پس ازpipe یک fork نیز انجام شود، فرآیند فرزند دو پروندة متعلق به لوله را (که در فرآیند پدر وجود دارند) در اختیار خواهد داشت. اکنون اگر فرآیند پدر بخواهد برای فرزند اطلاعات بفرستد، باید یک لوله ایجاد کند و اطلاعات را در پرونده فقط نوشتنی بنویسد. فرآیند فرزند با خواندن از پروندة فقط خواندنی، این اطلاعات را دریافت می‌کند. فرآیند پدر چون به پروندة فقط خواندنی احتیاج ندارد، آن را می‌بندد. فرآیند فرزند نیز پروندة فقط نوشتنی را می‌بندد.

 

#include <unistd. H>

#include <stdio. H>

int main(void)

{

int pfd[2];

char *s = "OS-LAB";

char p[6];

if(pipe(pfd) == -1)

{

perror("Pipe()");

return 1;

}

if(write(pfd[1], s, strlen(s) + 1) == -1)

{

perror("write()");

return 2;

}

if(read(pfd[0], p, strlen(s)+1) == -1)

{

perror("read()");

return 3;

}

printf("Send string = %s\n", s);

printf("Received string = %s\n", p);

return 0;

}

 

شکل 10-2: ایجاد لوله

 

به برنامه شکل 9-2 توجه کنید. این برنامه یک لوله ایجاد کرده و ابتدا در پروندة فقط نوشتنی آن، می‌نویسد و سپس سعی می‌کند از پروندة فقط خواندنی، بخواند. شاید این مورد چندان اهمیت لوله را نشان ندهد، ولی اگر بعد از این مراحل یک فرزند ایجاد شود، آن نیز می‌تواند محتویات پروندة فقط خواندنی را که توسط پدرش در پروندة فقط نوشتنی قرار گرفته است، بخواند.

اگر پس از ایجاد لوله، یک فرزند ایجاد شود، فرآیندهای پدر و فرزند (هر دو)، یک پروندة فقط نوشتنی و یک پروندة فقط خواندنی خواهند داشت. پدر می‌تواند در پروندة فقط نوشتنی بنویسد و خودش یا فرزندش از پروندة فقط خواندنی بخوانند و یا بالعکس فرزند در پروندة فقط نوشتنی بنویسد و خودش یا پدرش از پروندة فقط خواندنی بخوانند. البته اینکه چه موقع یک فرآیند شروع به خواندن کند تا از وجود اطلاعات معتبر در پرونده مطمئن شود، نیازمند مدیریت کافی فرآیندها و پرونده‌ها است (مثلاً پدر خود را منتظر نگه دارد کند تا فرزند اطلاعات را بنویسد و...).


لازم به ذکر است که معمولا ایجاد لوله به خاطر برقرار ساختن ارتباطی یک طرفه از پدر به فرزند یا بالعکس است. ایجاد ارتباطات دیگر نیاز به مدیریت قوی فرآیندها و پرونده‌ها دارند (مثل ایجاد ارتباط دو طرفه بین دو فرآیند با یک لوله) و یا چندان لازم نیستند (مثل ارتباط یک فرآیند با خودش). برای برقراری ارتباط دوطرفه بین دو فرآیند، بهتر است دو لوله در جهات مخالف هم ایجاد شود (مطابق شکل 10-3).

 

 

 

 

 

 

 

 


شکل 10-3: ایجاد ارتباط دوطرفه بین فرآیندها

10-1-2 دوطرفه‌کردن (Dup)

معمولاً خروجی فرآیندهای پدر و فرزند همه در یک جا ارائه می‌گردد و باعث بروز مشکلاتی می‌شود. اکثر فرمان‌های Linux، خروجی خود را در پروندة خروجی استاندارد (stdout) می‌نویسند. اما گاهی لازم می‌شود که خروجی در جای دیگری مثلاً یک پرونده نوشته شود: می‌خواهیم که یک فرآیند فرزند یکی از فرمان‌های پوسته، مثل ps یا ls را اجرا کند تا خروجی آن از طریق یک لوله به فرآیند پدر منتقل شود. ولی خروجی این دستورات به‌طور طبیعی در خروجی استاندارد (صفحة نمایش) نوشته می‌شود که مطلوب ما نیست. برای رفع این مشکلات، Linux، امکان دوطرفه ‌کردن Duplicat)) شناسة یک پرونده را در اختیار گذاشته است.

فراخوان سیستمی دوطرفه‌کردن (fd)، اولین پروندة بسته (بلااستفاده) را متناظر با fd می‌کند (عملاً پروندة fd از دو طریق قابل دسترسی است؛ اول از طریق خودش و دیگری از طریق پروندة متناظر شده). بنابراین اگر ما پروندة شمارة 1 را که مخصوص خروجی استاندارد (stdout) است، ببندیم و پروندة شمارة صفر هنوز باز باشد، عبارت دوطرفه‌کردن (fd) باعث خواهد شد که از این به بعد هر چیز که قرار است در پروندة شمارة 1 (stdout) نوشته شود، به پروندة fd منتقل‌ شود و دیگر چیزی بر صفحة نمایش نشان داده نشود. اگر این پرونده (fd)، پروندة فقط نوشتنیِ یک لوله باشد، فرآیند آن سوی لوله نیز از چیزی که قرار بود به صفحه نمایش منتقل شود، آگاه خواهد شد.

گونة بهتر شده‌ای از دوطرفه‌کردن با نام dup2 نیز وجود دارد که دو پارامتر می‌گیرد. به‌جای آنچه که در بخش قبلی با dup انجام گرفت، کافی است عبارت dup2(fd , 1) نوشته شود. در این حالت پروندة مشخص شده با پارامتر دوم در صورت باز بودن بسته می‌شود و متناظر با پرونده پارامتر اول می‌شود.

برای مثال اگر بخواهید خروجی ls را در یک پرونده ذخیره کنید، فرمان زیر را وارد کنید:

$ls > test1

 

با این فرمان، خروجی ls به جای خروجی استاندارد، در پروندة test1 نوشته می‌شود. اما در پشت پرده کاری مطابق شکل 4-10 انجام می‌گیرد:

 


شکل 10-4: نحوة عملکرد dup2

 

فرآیند پوسته با استفاده از fork فرمان ls را اجرا می‌کند و فرآیند فرزند، قبل از احضار یک تابع exec، پروندة test1 را ایجادکرده و dup2 را احضار می‌کند تا آنچه که در شکل 10-5 نشان داده شده است، انجام گیرد. ملاحظه می‌شود که نیازی به کپی‌کردن محتویات پروندة مربوط به صفحه نمایش (stdout) در پروندة test1 نیست و همة کارها از طریق شناسة پرونده‌ها انجام می‌گیرد. سهولت این کار به این دلیل است که همه چیز از دیدگاه Linux، پرونده است.

برای مثال مذکور، باید عملیاتی شبیه به قطعه کُد شکل 10-5 در shell اجرا شود.

 

pid = fork();

switch(pid)

{

case -1: /* error */

case 0: /* child */

{

int fd;

fd = creat("filelist",0666); /*creat write & read file*/

if(fd == -1)

exit(1);

if(dup2(fd, 1) == -1)

exit(2);

if(execl("/bin/ls", NULL) == -1)

exit(3);

}

default: /* parent */

}

شکل 10-5: نحوة عملکرد shell در موقع تغییر مسیر دادن خروجی

به نحوة احضار dup2 توجه کنید. این تابع نیز مثل بقیة فراخوان‌های سیستم در صورت بروز خطا -1 باز می‌گرداند. ملاحظه می‌کنید که قبل از انجام execl لازم است عملیاتی انجام شود. در این مثال لازم است که ابتدا یک پرونده ایجاد شده و dup2 احضار شود تا نوبت به execl برسد.

10-1-3 سیگنال‌ها و فراخوانی آن‌ها در Linux

سیگنال پیامی‌ است که یک فرآیند به دیگری می‌فرستد و فرآیند دیگر با دریافت آن، کار به خصوصی را انجام می‌دهد. معانی و کارکردهای از پیش تعریف شده‌ای برای اغلب سیگنال‌ها وجود دارند. برای مثال، سیگنال SIGFPE به یک فرآیند اطلاع می‌دهد که یک خطای floating point اتفاق افتاده است و سیگنال SIGKILL فرآیند را مجبور می‌کند تا به کار خود پایان دهد.

یک فرآیند را می‌توان طوری سازمان داد تا بعضی از سیگنال‌ها را نادیده بگیرد و یا در جواب آن‌ها کار دیگری انجام شود. این کار به وسیلة فراخوان سیستم signal امکان‌پذیر است که دراینجا ما به آن نمی‌پردازیم.

یک سیگنال را می‌توان از داخل برنامه به وسیلة فراخوان سیستم kill، به فرآیند یا فرآیندهای دیگر ارسال کرد. قالب کلی آن به شکل زیر است:

int kill (pid_t pid , int sig)

pid شماره فرآیندی است که می‌خواهیم سیگنال به آن فرستاده شود و sig شمارة سیگنال مورد نظر است. می‌توان به جای sig، نام سیگنال مورد نظر را ذکر کرد. در صورت موفقیت، مقدار صفر و در غیر این صورت یک 1- برگردانده خواهد شد.

در این آزمایش، ما از دو سیگنال با نام‌های SIGCONT و SIGSTOP استفاده خواهیم کرد. سیگنال SIGCONT، یک فرآیند متوقف شده را دوباره به کار می‌اندازد و سیگنال SIGSTOP، یک فرآیند در حال کار را متوقف می‌سازد.

10-1-4 الگوریتم مرتب‌سازی موازی زوج‌فرد (OddEven)

مرتب‌سازی موازی زوج‌فرد که در شکل 10-6 نشان داده شده است، نمونة ساده‌ای از الگوریتم‌های موازی است. الگوریتم‌های موازی، الگوریتم‌هایی هستند که قسمت‌های مختلف آن می‌توانند به موازات هم انجام شوند. این الگوریتم‌ها از سرعت بالایی برخوردار هستند و پیاده‌سازی واقعی آن‌ها در یک محیط چندپردازنده‌ای امکان‌پذیر است، ولی می‌توان در سیستم‌های چندوظیفه‌ای نیز آن‌ها را پیاده‌سازی کرد و بخش‌های موازی این الگوریتم‌ها را بر عهدة فرآیندهای مختلف گذاشت. این الگوریتم‌ها در سیستم‌های چند وظیفه‌ای که تنها یک پردازنده دارند، سرعت واقعی خود را نخواهند داشت، زیرا در نهایت تمامی‌ عملیات توسط یک پردازنده انجام می‌شود. اکنون به شرح این الگوریتم می‌پردازیم و سپس با تغییرات اندکی، آنرا بوسیلة فرآیندها در Linux پیاده‌سازی خواهیم کرد:

فرض می‌کنیم n پردازنده با نام‌های P1، P2، ... و Pn و n ورودی با نام‌های X1، X2، ... و Xn وجود دارند. هر پردازنده یک ورودی می‌گیرد و آن را در تنها ثبات خود قرار می‌دهد. هدف، مرتب‌سازی ورودی‌ها در میان پردازنده‌ها است، به‌طوری‌که کوچک‌‌ترین ورودی در ثبات P1 و دومین کوچک‌‌ترین ورودی در P2 و بالاخره بزرگ‌‌ترین ورودی در Pn قرار گیرد. در حالت تعمیم یافته که بیش‌‌تر مورد نظر ماست، هر پردازنده می‌تواند بیش از یک ورودی بگیرد و در ثبات‌های خود قرار دهد که در این مورد بعدا بحث خواهد شد.

 

پردازنده‌ها به شکل خطی به هم متصل هستند و هر پردازنده فقط می‌تواند با همسایة سمت راست خود ارتباط برقرار کند. بنابراین عمل مقایسه و تعویض بر روی عناصری می‌توانند انجام شوند که در صف پردازنده‌ها پشت سر هم باشند.

همان‌طورکه گفتیم، هر پردازنده عدد موجود در تنها ثبات خود را با عدد موجود در ثبات همسایة سمت راستش مقایسه می‌کند و بنابراین تعویض، زمانی اتفاق می‌افتد که ترتیب اعداد موجود در ثبات‌های دو پردازندة به‌دنبال هم، درست نباشد. سپس این روند برای اعداد بعدی تکرار خواهد شد تا محتوای موجود در ثبات پردازنده‌ها ترتیب صحیحی داشته باشند. به نظر می‌رسد که الگوریتم در بدترین حالت باید n-1 بار انجام شود و آن وقتی است که یک ورودی از یک طرف صف پردازنده‌ها به سوی دیگر آن حرکت کند.

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


یک مثال عددی از این الگوریتم در شکل 10-7 آمده است. در این مثال، مرتب سازی پس از شش مرحله کامل گشته است. در حالت کلی، پیش‌بینی تعداد مراحل اجرای الگوریتم کار سختی است و در این آزمایش بهتر است اجازه دهیم تا الگوریتم در بدترین حالتِ اجرای خود، کار کند.

 

شکل 10-6: الگوریتم مرتب‌سازی موازی زوج‌فرد (OddEven)

 

Algorithm Odd-Even sort (X, n)

Input: X (an array in the range 1 to n, such that Xi resides at Pi)

Output: X (the array in sorted order, such the i th smallest element is in Pi)

begin

do in parallel [n/2] times

P2i-1 and P2i compare their elements and exchange them if necessary;

for all i, such that 1 <= 2i <= n

P2i and P2i+1 compare their elements and exchange them if necessary;

for all i, such that 1<= 2i <= n

if n is odd, then this step is done only [n/2] time

end

شکل 10-7: مثال عددی از مرتب‌سازی زوج‌فرد

اکنون فرض کنید n فرآیند با نام‌های P1، P2، ... و Pn و آرایه‌ای شامل m داده وجود دارند (m>n). هدف، مرتب کردن عناصر این آرایه است به‌طوری‌که کوچک‌‌ترین عنصر در اولین خانة آرایه و بزرگ‌‌ترین عنصر در آخرین خانة آرایه قرار گیرند. مقایسة داده‌ها و تعویض محل آن‌ها وقتی ممکن است که این داده‌ها در آرایه پشت سر هم باشند. در این الگوریتم، آرایه به n قسمت حتی‌الامکان مساوی تقسیم می‌شود. سپس هر قسمت در اختیار یکی از فرآیندها قرار می‌گیرد و فرآیند مربوطه، قسمت مورد نظر خود را مرتب می‌کند. برای مثال اگر سه فرآیند داشته باشیم، یک آرایة 16 عنصری را می‌توان در میان این فرآیندها به دو آرایة پنج عنصری و یک آرایة شش عنصری تقسیم کرد. هر کدام از این فرآیندها بر روی یکی از این بخش‌ها کار مرتب‌سازی را به شکل مقایسة دو عدد دنبال هم در آرایه و تعویض آن‌ها در صورت نیاز، ادامه می‌دهد. پس از اینکه هر کدام از فرآیندها یک بار حلقة خود را طی کرد، باید منتظر فرآیندهای دیگر بماند تا آن‌ها نیز کار خود را تمام کنند. سپس تمام فرآیندها با هم دور بعدی حلقه را شروع می‌کنند. در مرحلة دوم، هر کدام از فرآیندها بخشی از آرایه را که در اختیار دارند یک خانه به جلو تغییر می‌دهند. برای مثال، فرآیند اول در گام اول روی عناصر اول تا ششم و در گام بعدی روی عناصر دوم تا هفتم و در گام سوم دوباره روی عناصر اول تا ششم کار خواهد کرد و این روند تا آخر تکرار خواهد شد.

مهم‌‌ترین مشکل در پیاده‌سازی این الگوریتم، هماهنگ‌سازی و کنترل همزمانی فرآیندها است که این کار می‌تواند به وسیلة سیگنال‌ها انجام شود.

10-2 دستورکار

10-2-1 آزمایش‌های مربوط به لوله و Dup

1) قطعه برنامه‌ای را که در قسمت لوله آمده است، در پروندة Pipe1.c قرار داده‌ایم. آن را ترجمه و اجرا کنید و مشاهدات اجرای برنامه را بنویسید.

2) در پروندة dup.c برنامه‌ای بنویسید که معادل دستور زیر باشد. قبل از نوشتن برنامه، طرح خود را آماده و در صورت لزوم با مسؤل آزمایشگاه در میان بگذارید:

ls -l > test1

چگونه می‌توان عملیات زیر را انجام داد:

$ ls >> test1

تذکر: علامت >> برای اضافه (Append) کردن به‌کار می‌رود.

10-2-2 آزمایش‌های مربوط به بخش ‌‌signal

1) برنامه‌ای بنویسید که پس از ایجاد فرآیند فرزند، آن را متوقف کرده و عملیات خود را انجام دهد (عملیات اختیاری است) و پس از انجام عملیات خود فرآیند فرزند را راه‌اندازی کند.

10-2-3 بخش اختیاری آزمایش (الگورریتم Oddeven Sort)

1) برنامه‌ای بنویسید که سه فرآیند فرزند ایجاد کند و سپس آن‌ها را با سیگنال‌ها هماهنگ کند. کار هر کدام از فرآیندهای فرزند این است که در داخل یک حلقه، 10 بار پیغامی را چاپ کنند. می‌خواهیم طوری فرآیندها را هماهنگ کنیم که تا بارِ اول حلقة همة آن‌ها تمام نشده است، وارد بار دوم حلقه نشوند.

2) پروندة oddevensort.c را باز کرده و قسمت‌های مختلف آن را ببینید. تابع مرتب‌سازی بر اساس الگوریتم مرتب‌سازی موازی زوج‌فرد پیاده‌سازی شده است. با توضیحات مربی آزمایشگاه این برنامه را تکمیل کنید.

تاریخ به روز رسانی:
1401/11/16
تعداد بازدید:
725
Powered by DorsaPortal