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

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

خوب، آیا در مورد اینکه فراخوانی های سیستمی چطور در لینوکس کار میکند کنجکاو هستید؟ آیا میخواهید نحوه ردگیری فراخوانی های سیستمی را یاد بگیرد؟ آیا کنجاوید که کدام فراخوانی های سیستمی باید مانیتور شوند و چرا؟

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

اجازه دهید با کمی مفاهیم نظری شروع کنیم.

فراخوانی های سیستمی در لینوکس چگونه کار می کنند؟

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

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

فراخوانی های سیستمی شبیه فراخوانی توابع برنامه ای هستند که از آن استفاده میکنند، اما در عمل کمی از فراخوانی توابع پیچیده تر هستند و به یک انتقال از حالت کاربر (user mode) به حالت هسته نیاز دارند. 

تصویر زیر ترتیب مراحلی را که فراخوانی تابع fwrite از توابع کتابخانه استاندارد زبان C شامل میشود را نشان میدهد. توجه کنید که برای آسانی فهم کمی ساده سازی شده است.

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

  1. تابع fwrite به همراه بقیه توابع استاندارد زبان برنامه نویسی سی، در کتابخانه glibc که یکی از اجزای اصلی لینوکس هست، تعریف شده است.
  2. تابع fwrite در واقع یک پوشاننده (wrapper) برای فراخوان کتابخانه ای write است.
  3. write، در رجیسترهای پردازنده ID فراخوان سیستمی ( که برای write مقدار آن 1 است ) و آرگومانهای دیگر را لود میکند و سپس باعث میشود که پردازنده به حالت kernel سوئیچ کند. روشی که این کار انجام میشود به معماری پردازنده و گاها به مدل آن بستگی دارد. برای مثال پرازنده های x86 معمولا از وقفه 80 استفاده میکنند، در حالیکه پردازنده های x64 از دستور پردازنده syscall استفاده میکنند.
  4. پردازنده که حالا در فضای هسته کار میکند، جدول syscall را با ID فراخوانی سیستمی تغذیه میکند و اشاره گر تابع در offset 1 را استخراج و آن را فراخوانی میکند. این تابع (sys_write) نسخه هسته از نوشتن یک فایل است.
حرف منو قبول ندارید؟ پس کد زیر رو امتحان کنید


# ----------------------------------------------------------------------------------------
# helloworld.s. Hello world in assembly!
# ----------------------------------------------------------------------------------------

     .global _start

    .text
_start:
# write(1, message, 13)
    mov     $1, %rax                # system call ID. 1 is write
    mov     $1, %rdi                # file handle 1 is stdout
    mov     $message, %rsi          # address of string to output
    mov     $13, %rdx               # string length
    syscall                         # system call invocation!

# exit(0)
    mov     $60, %rax               # system call ID. 60 is exit
    xor     %rdi, %rdi              # we want return code 0
    syscall                         # system call invocation!
message:
    .ascii  "Hello, world\n"

کد بالا را با دستور زیر کامپایل کنید:


$ gcc -o helloworld -nostdlib helloworld.s

بله این سطح پایین ترین پیاده سازی از برنامه hello world روی یک پردازنده x64 است. این برنامه در خروجی استاندارد فقط با هسته لینوکس، عبارت hello wrold را پرینت میکند.

اگر کدنویسی سطح پایین شمارو عصبی میکنه، glibc تابعی به نام syscall رو ارائه میده که میتونید با اون از رابط فراخوان سیستمی استفاده کنید.


#include <sys syscall.h="">

int main() {
    syscall(SYS_write, 1, "Hello, world\n", 13);
    return 0;
}

چگونه فراخوان های سیستم را مانیتور کنیم؟

ابزارهایی وجود دارند که برای مانیتور فراخوانهای سیستمی استفاده میشوند. مشهورترین مورد، strace است که روی اکثر سیستم عامل ها وجود دارد و احتمالا شما هم در سیستمتان آن را دارید. در ساده ترین شکل آن، میتوانید آن را و سپس نام پروسس مورد نظر را که میخواهید مانیتور نمایید، فراخوانی کنید:

> strace myprogram

sysdig فرزند نوظهور این خانواده است و میتوانید آن را با این دستورات نصب کنید. کارهای زیادی رو میتوانید با استفاده از sysdig انجام بدید ولی در ساده ترین حالت شما فقط اون رو اجرا میکنید و تمام فراخوانی های سیستمی رو که رخ میدهند رو مشاهده میکنید. بخاطر اینکه این فراخوانی ها خیلی زیاد هستند میتونید از فیلتر استفاده کنید تا خروجی این برنامه را قابل استفاده تر کنید:

> sysdig proc.name=myprogram

فراخوان های سیستمی که باید مانیتور کنید:

clone
چکار میکند؟

پروسسهای جدید ایجاد میکند.

برای چی باید مانیتورش کنم؟

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

فیلتر sisdig

evt.type=clone

نکات قابل توجه

clone تنها فراخوان سیستمی است که دوبار برمیگرداند! یک بار در پروسس والد و یک بار در پروسس فرزند. نگذارید گیجتان کند. clone فراخوان پرهزینه ای است پس نگذارید برنامه تان زیاد از آن استفاده کند.

execve
چکار میکنه؟

یک برنامه جدید را اجرا میکند

چرا باید مانیتورش کنیم؟

execve تنها ورودی هسته لینوکس برای اجرای یک برنامه است. در فضای کاری کاربر (user space) معادل های مختلفی مانند execl و fexcve وجود دارند اما همه آنها در نهایت از فراخوان سیستمی execve استفاده میکنند. همیشه execve را پس از یک clone مشاهده خواهید کرد و مانیتور کردن آن به شما خواهد گفت که چه برنامه ای در سیستم در حال اجرا شدن است. هر برنامه، اسکریپت و وظیفه cron ای باید از میان execve بگذرد.

فیلتر sisdig مربوطه

evt.type=execve

chdir
یعنی چی؟

دایرکتوری را که پروسس جاری در آن کار می کند عوض میکند

چرا باید مانیتور بشه؟

اگر execve هر خط دستور که اجرا می شود را به شما نشان میدهد، chdir به شما هر پوشه ای که دیده میشود را نشان میدهد. دستور زیر را اجرا کنید 

> sysdig evt.type=chdir

و سپس یک دایرکتوری را از یک شل دیگر نگاه کنید.

sysdig filter

evt.type=chdir

open/creat
چکار میکند؟

یک فایل یا ابزار را باز و یا ایجاد میکند

چرا باید مانیتورش کنید؟

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

فیلتر sysdig

evt.type=open

evt.type=creat

نکات

استفاده از فیلترها ردگیری این فراخوان سیستمی در sysdig را خیلی مفیدتر میکند. برای مثال :

> evt.type=open and proc.name=evilproc and file.name contains /etc
> evt.type=open and proc.name=niginx and file.name contains .log
connect
یعنی چی؟

یک ارتباط روی سوکت را شروع میکند

برای چه باید مانیتور شود؟

تنها ورودی کرنل برای برقراری یک ارتباط شبکه است. هربار که یک process روی ماشین شما میخواهد به چیزی وصل شود این فراخوان سیستمی را خواهید دید.

فیلتر sysdig

evt.type=connect

نکات

مجددا خاطر نشان میشود که از فیلترها میتوانید برای کارهای جالبتر استفاده کنید. برای مثال:

> sysdig evt.type=connect and fd.port=80
> sysdig "evt.type=connect and fd.sip!=127.0.0.1"
accept
چکار میکند؟

این فراخوان سیستمی connect را انعکاس میدهد، بطوریکه هر بار یک ارتباط با سرور روی ماشین صورت گرفت این فراخوان دیده میشود.

فیلتر sysdig

evt.type=accpet

نکات

مجددا فیلترها. برای مثال:

> sysdig evt.type=accept and fd.port=80
> sysdig "evt.type=accept and proc.name=nginx and fd.port!=80"
read/write
چه میکنند؟

read و write معادلهای مختلفی در رابط فراخوان سیستمی لینوکس دارند: readv, writev, preadv, pwritev, send, recv, sendto, recvfrom, sendmsg, sendmmsg, recvmsg, recvmmsg. همه آنها کار یکسانی انجام میدهند و داده را از file descriptor میخوانند و در آن می نویسند که این کار قبل انجام I/O است.

چرا باید مانیتور شوند؟

مشاهده این فراخوانها خیلی مفید است زیرا تقریبا هرچیزی در لینوکس در یک تشریح کننده فایلی (file descriptor) اتفاق می افتد. آنها دسترسی به فایلها، تبادل داده ها در شبکه و فعالیتها روی پایپها و سوکتهای یونیکس را نشان میدهند.

فیلتر sysdig

evt.is_io=true

یادداشتها

sysdig ابزاری را ارائه میکند تا براحتی فعالیتهای I/O را مشاهده کنید. میتوانید آن را همراه با یک فیلتر استفاده کنید:

> sysdig -c echo_fds fd.name=/etc/password
> sysdig -c echo_fds proc.name=nginx and fd.port!=80
unlink/rename
چکار میکنند؟

حذف یا تغییر نام فایلها

چرا شما باید مانیتور شوند

حال که open/creat به شما میگویند که چه وقت فایلهای جدید ایجاد میشوند و نسخه های مختلف read/write به شما میگویند که کی این فایلها تغییر میکنند، unlink و rename به شما میگویند که کی فایلها حذف یا تغییرنام داده میشوند.

فیلتر sysdig

evt.type=unlink

evt.type=rename

brk/mmap/munmap

آنها چکار میکنند؟

تخصیص و یا آزادسازی حافظه

چرا باید مانیتور شوند؟

اگر میخواهید بدانید که یک پروسس چگونه حافظه اختصاص میدهد یا حافظه را آزاد میکند این فراخوانهای سیستمی را باید مانیتور کنید. brk بصورت سنتی توسط malloc() استفاده میشود در حالیکه mmap/mmunmap مفیدتر هستند و برای چندین کار از جمله اشتراک حافظه و یا نگاشت فایلها به حافظه، به کار میروند.

فیلتر sysdig

evt.type=brk

evt.type=mmap

evt.type=munmap

نکات

 مراقب باشید. مدیریت حافظه موضوع پیچیده ای است و در این پست نمیگنجد. شما هیچ وقت یک brk یا mmap را پشت سر یک malloc() یا new() در برنامه خود نمی بینید، زیرا یک برنامه عادی محتملا از حداقل یک یا موجودیت قبل از تخصیص حافظه عبور خواهد کرد. برای مثال glibc یک تخصیص دهنده حافظه دارد که حافظه را بصورت تکه های بزرگ از کرنل درخواست میکند و سپس آنها را حالت کاربر مدیریت میکند. زباله جمع کن زبان مورد علاقه مبتنی بر VM شما نیز کار مشابهی را انجام میدهد.

select/poll
چکار میکند؟

منتظر می ماند

چرا باید مانیتور شود؟

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

فیلتر sysdig

evt.type=select

evt.type=poll

kill
چکارمیکد؟

سیگنال میفرستد

چرا باید مانیتور شود؟

kill بوسیله یک پروسس استفاده میشود تا سیگنالی را به پروسسی دیگر ارسال کند.

فیلتر sysdig

‌‌evt.type=kill

در پایان

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

منبع : The Fascinating World of Linux System Calls