با استفاده از لینوکس و C، کنترل دقیقی بر عملکردهای زمان بندی خود به دست آورید.
مکانیسمهای تایمر به شما امکان میدهند هسته سیستمعامل را برنامهریزی کنید تا زمانی که یک زمان از پیش تعیینشده سپری شده است، برنامه را مطلع کند. شما معمولاً با ارائه دو اطلاعات از آنها استفاده خواهید کرد. ابتدا باید مشخص کنید تایمر قبل از اعلان چقدر زمان ببرد. ثانیاً، باید یک تابع پاسخ به تماس را آماده کنید تا در زمان وقوع آن اعلان عمل کند.
رویکرد سنتی به تایمر
مکانیسم های تایمر در سیستم های مبتنی بر لینوکس و یونیکس برای پاسخگویی به نیازهای مختلف تکامل یافته اند. رویکردهای مختلف می تواند به شما در حل انواع مختلف مشکلات کمک کند. با این حال، شما اغلب اولین نسخه از مکانیسم alarm() را می بینید که هنوز در حال استفاده است.
عملکرد هشدار ساده ترین راه برای استفاده از تایمر است. در اینجا نمونه اولیه آن است:
unsigned int alarm(unsigned int seconds);
با استفاده از این روش فقط می توانید زمان را در ثانیه کامل مشخص کنید. وقتی زمان تمام شد، سیستم عامل سیگنال SIGALRM را به برنامه شما ارسال می کند. برای پردازش انقضای تایمر در برنامه خود، باید یک تابع تماس مجدد نیز تعریف کنید.
در اینجا مثالی از عملکرد کنترل کننده سیگنال آورده شده است:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
void timer_callback(int signum)
{
time_t now = time(NULL);
printf("Signal %d caught on %li", signum, now);
}
int main()
{
signal(SIGALRM, timer_callback);
alarm(1);
sleep(3);
return 0;
}
این کد یک سیگنال SIGALRM را پس از 1 ثانیه افزایش می دهد. اگر می خواهید تاخیر تایمر را به پنج ثانیه افزایش دهید، فقط زنگ هشدار (5) را صدا بزنید. برای متوقف کردن تایمر، مقدار 0 را ارسال کنید: زنگ (0).
وقتی زمان تمام شد، تایمر مورد استفاده شما به صورت دوره ای راه اندازی مجدد نمی شود. به عنوان مثال، اگر میخواهید یک ثانیه دیگر به تأخیر بیفتید، باید مکانیسم را با یک فراخوانی زنگ دیگر راهاندازی مجدد کنید.
علیرغم سهولت استفاده، این روش دارای معایبی است:
- فقط یک تایمر در یک زمان.
- بدون پشتیبانی از تایمر دوره ای
- شما فقط می توانید دوره زمانی را در مضرب ثانیه کامل ارائه دهید.
- هیچ راهی برای دانستن مدت زمان باقی مانده در یک تایمر وجود ندارد.
کد نمونه داده شده در بالا را به عنوان alarm.c ذخیره کنید. هنگامی که آن را کامپایل و اجرا می کنید، برنامه پس از یک ثانیه تابع timer_callback را فراخوانی می کند. سپس برای دو ثانیه باقیمانده به دلیل خط خواب (3) منتظر می ماند، سپس خاتمه می یابد.
$ gcc -o alarm alarm.c
$ time ./alarm
Signal 14 caught on 1653490465
real 0m1.004s
user 0m0.000s
sys 0m0.003s
دلیل استفاده از دستور time این است که بتوانیم زمان ها را ببینیم. اما اگر به نتیجه نگاه کنید، کل زمان اجرا سه ثانیه نیست. این به دلیل سیگنال SIGALRM از زنگ هشدار (1) است، زمانی که اولین ثانیه بالا میرود، در حالی که syscall ناشی از عملکرد خواب (3) در حال اجرا است. هنگامی که این سیگنال می رسد، سیستم سیگنال آغاز شده برای خواب (3) را قطع می کند.
استفاده از تایمر فاصله
مکانیسم تایمر فاصله برای اولین بار در نسخه 4.2 BSD در دسترس بود. بعداً توسط POSIX استاندارد شد. مزایای اصلی آن نسبت به روش تایمر مبتنی بر alarm() سنتی عبارتند از:
- وضوح میکروثانیه را ارائه می دهد.
- این اجازه می دهد تا اندازه گیری زمان را با جزئیات بیشتر در سه حالت مختلف کنترل کنید.
- می توان آن را یک بار تنظیم کرد و به صورت دوره ای کار کرد.
- می توان فهمید که در هر لحظه چه مدت وجود دارد.
نمونه های اولیه عملکرد مورد استفاده برای عملیات تایمر بازه ای به شرح زیر است:
#include <sys/time.h>
int setitimer(int which, const struct itimerval *newValue, struct itimerval *oldValue);
int getitimer(int which, struct itimerval *value);
struct itimerval
{
struct timeval itInterval;// next value
struct timeval itValue;// current value
};
struct timeval
{
long tv_sec;
long tv_usec;
};
اگر میخواهید یک تایمر بازهای تنظیم کنید، باید از ساختار itimerval استفاده کنید. شما باید یک مقدار را با استفاده از این ساختار به عنوان آرگومان دوم به تابع settimer ارسال کنید.
به عنوان مثال، یک تایمر بازه زمانی که برنامه شما را برای 1 ثانیه و سپس هر 300 میلی ثانیه به شما اطلاع می دهد، می تواند به صورت زیر تنظیم شود:
struct itimerval newTimer;
struct itimerval oldTimer;
newTimer.itValue.tv_sec = 1;
newTimer.itValue.tv_usec = 0;
newTimer.itInterval.tv_sec = 0;
newTimer.itInterval.tv_usec = 300 * 1000;
setitimer(ITIMER_REAL, &newTimer, &oldTimer);
اگر قبل از تنظیم مقادیر جدید یک تایمر بازهای فعال وجود داشته باشد، مقادیر آن به آدرس متغیری از نوع زمانبندی دادهشده به پارامتر سوم تابع منتقل میشود.
شما می توانید سه نوع مختلف تایمر را با مکانیسم تایمر فاصله تنظیم کنید. نوع تایمر را در اولین پارامتر setitimer():
نوع تایمر
علامت
توضیح
ITIMER_REAL
SIGALRM
مستقل از زمان صرف شده توسط برنامه، در کل زمان سپری شده محاسبه می شود.
ITIMER_VIRTUAL
SIGVTALRM
در طول زمانی که برنامه فقط در حالت کاربر اجرا می شود محاسبه می شود.
ITIMER_PROF
SIGPROF
بر روی مجموع زمان صرف شده توسط برنامه در هر دو حالت کاربر و سیستم محاسبه می شود.
از این جدول می بینید که نوع ITIMER_REAL یک سیگنال SIGALRM ارسال می کند، درست مانند تابع alarm().
استفاده از تایمر بازه ای و آلارم() در یک برنامه گیج کننده خواهد بود. اگرچه میتوانید زمان باقیمانده را با (gettimer) بررسی کنید، اما استفاده همزمان از آنها منطقی نیست.
در اینجا مثالی از تعریف تابع کنترل کننده سیگنال با هدر اشکال زدایی آورده شده است:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<time.h>
#include<sys/time.h>
#include<errno.h>
#include<string.h>
#include"./debug.h"
void timer_callback(int signum)
{
struct timeval now;
gettimeofday(&now, NULL);
printf("Signal %d caught on %li.%03li ", signum, now.tv_sec, now.tv_usec / 1000);
}
int main()
{
unsigned int remaining = 3;
struct itimerval new_timer;
struct itimerval old_timer;
new_timer.it_value.tv_sec = 1;
new_timer.it_value.tv_usec = 0;
new_timer.it_interval.tv_sec = 0;
new_timer.it_interval.tv_usec = 300 * 1000;
setitimer(ITIMER_REAL, &new_timer, &old_timer);
signal(SIGALRM, timer_callback);
while (sleep(remaining) != 0)
{
if (errno == EINTR)
debugf("sleep interrupted by signal");
else
errorf("sleep error %s", strerror(errno));
}
return 0;
}
کد بالا با استفاده از تابع sleep() به مدت سه ثانیه صبر می کند. در طول این مدت، یک تایمر بازه ای، ابتدا برای یک ثانیه، سپس با فاصله 300 میلی ثانیه کار می کند.
برای درک بهتر، کد نمونه را با نام interval.c ذخیره و کامپایل کنید:
$ gcc -o interval interval.c
$ time ./interval
Signal 14 caught on 1653493614.325
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493614.625
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493614.925
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493615.225
debug: sleep interrupted by signal (main interval.c:36)
Signal 14 caught on 1653493615.525
...
همانطور که از خروجی می بینید پس از اجرا شدن تایمر، تابع callback را هر 300 میلی ثانیه فراخوانی می کند.
با این حال، پس از کمی صبر کردن، متوجه خواهید شد که برنامه خاتمه نمی یابد. همچنان هر 300 میلیثانیه تابع تماس را اجرا میکند. اگر مقدار فاصله را به میلی ثانیه افزایش دهید، خواهید دید که برنامه خاتمه می یابد. این به دلیل ناحیه استفاده از تابع sleep() است.
اهمیت استفاده از تایمر در لینوکس
به خصوص برای کاربردهای بلادرنگ، مکانیسم تایمر از اهمیت بالایی برخوردار است. این نیز راه حلی است که برای بهینه سازی عملکرد استفاده می شود. شما حتی می توانید از آن برای اندازه گیری زمان آپلود یا تأخیر در برنامه خود استفاده کنید. استفاده از مکانیسم های تایمر برای پیگیری زمان سپری شده و رویدادهای انتقال زمان مهم است.