راهنمای مدیریت سیگنال در Rust

نوشته شده توسط Eze Sunday✏️
سیگنال یک وقفه نرم افزاری است که توسط سیستم عامل یا فرآیند دیگری برای اطلاع از یک رویداد به یک فرآیند ارسال می شود. به عنوان مثال، وقتی سعی می کنید فشار دهید Control+C
در حالی که برنامه شما روی یک ترمینال اجرا می شود، فرآیند را خاتمه می دهد، درست است؟ این یکی از رایج ترین سیگنال ها و کنترل سیگنال است که می توانید در عمل مشاهده کنید. ما نحوه کنترل آن سیگنال و سایر سیگنال ها را در Rust بررسی خواهیم کرد.
یک سیگنال می تواند توسط چیزهای مختلفی مانند سخت افزار، سیستم عامل، ورودی کاربر یا سایر فرآیندها راه اندازی شود. هنگامی که یک فرآیند سیگنالی را دریافت می کند به این معنی است که یک رویداد رخ داده است و فرآیند بسته به نوع سیگنال می تواند اقدام خاصی انجام دهد. به عنوان مثال، ممکن است فرآیند نیاز به توقف اجرا، راه اندازی مجدد، یا رسیدگی به یک خطا داشته باشد.
در این مقاله، هدف سیگنال ها و نحوه مدیریت سیگنال ها در زبان برنامه نویسی Rust را کشف خواهیم کرد. بیایید شروع کنیم، درست است؟ 🦀
پرش به جلو:
مقدمه ای بر سیگنال ها و مدیریت سیگنال در Rust
مشخص شده است که سیگنال ها به عنوان اعلان رویدادها عمل می کنند. درست مانند نحوه واکنش ما به اعلانها در زندگی روزمرهمان، زمانی که از رویدادی به شما اطلاع داده میشود، از شما انتظار میرود که یا مسئولیت آن را بپذیرید و به آن رسیدگی کنید یا اینکه آن را نادیده بگیرید. به طور مشابه، سیگنالهای سیستمعامل یک فرآیند را قادر میسازد تا در پاسخ به یک رویداد راهاندازی شده اقدامی انجام دهد یا کاری انجام ندهد.
برای مثال، سیگنالها میتوانند یک فرآیند در حال اجرا را متوقف یا متوقف کنند، کاربر را از یک خطا مانند استثناء ممیز شناور مطلع کنند، یا اطلاعاتی مانند زنگ هشدار سیستم را ارائه دهند. وقتی چنین سیگنالهایی دریافت میشوند، ممکن است یک برنامه نیاز به بستن دستگیرههای باز داشته باشد تا منابع سیستم را آزاد کند یا هر فعالیتی را که رویداد ممکن است تحت تأثیر قرار دهد خاتمه دهد. چنین موردی است که یک برنامه زمانی که کاربر فشار می دهد، خارج می شود Control+C
.
بررسی انواع سیگنال ها
چندین نوع سیگنال وجود دارد. برخی را می توان مدیریت کرد، در حالی که برخی دیگر نمی توانند. جدول زیر برخی از انواع سیگنال را با کدهای موجود بر اساس استانداردهای POSIX نشان می دهد. این استاندارد مجموعه ای از استانداردها است که API ها را برای سیستم عامل های مشابه یونیکس، از جمله لینوکس، macOS و انواع مختلف یونیکس تعریف می کند. به جدول زیر مراجعه کنید:
نوع سیگنال | استفاده کنید |
---|---|
SIGHUP ، کد: 1
|
این سیگنال زمانی که ترمینال کنترل کننده آن بسته یا قطع می شود به یک فرآیند ارسال می شود |
SIGINT ، کد: 2
|
هنگامی که کاربر فشار می دهد این سیگنال به یک فرآیند ارسال می شود Control+C تا اجرای آن قطع شود |
SIGQUIT ، کد: 3
|
این سیگنال مشابه است SIGINT اما برای شروع یک dump اصلی فرآیند استفاده می شود که برای اشکال زدایی مفید است
|
SIGILL ، کد: 4
|
این سیگنال زمانی به یک فرآیند ارسال می شود که سعی می کند یک دستور غیرقانونی را اجرا کند |
SIGABRT ، کد 6
|
این سیگنال هنگام فراخوانی فرآیند به آن ارسال می شود abort() تابع |
SIGFPE ، کد: 8
|
این سیگنال زمانی به فرآیندی فرستاده می شود که تلاش می کند یک عملیات حسابی غیرمجاز مانند تقسیم بر صفر انجام دهد. |
SIGKILL ، کد: 9
|
این سیگنال برای خاتمه فوری یک فرآیند استفاده می شود و نمی توان آن را گرفت یا نادیده گرفت |
SIGSEGV ، کد: 11
|
این سیگنال زمانی به یک فرآیند ارسال می شود که سعی می کند به حافظه ای که به آن اختصاص داده نشده است دسترسی پیدا کند |
SIGTERM |
این سیگنال به فرآیندی ارسال میشود تا درخواست شود که بهخوبی خاتمه یابد. کد: 15
|
SIGUSR1 ، کد: 10
|
این سیگنال ها را می توان توسط یک فرآیند برای اهداف سفارشی استفاده کرد |
SIGUSR2 ، کد: 12
|
مثل SIGUSR1 ، کد: 10
|
قبل از بحث در مورد کنترل سیگنال ها در Rust، اجازه دهید در مورد تنظیمات سیگنال صحبت کنیم.
درک شرایط سیگنال
تنظیم سیگنال به عملکرد پیش فرضی اشاره دارد که سیستم عامل زمانی که یک فرآیند سیگنال خاصی را دریافت می کند انجام می دهد. سه حالت احتمالی سیگنال عبارتند از:
-
Terminate
: این فرآیند بلافاصله بدون هیچ شانسی برای پاکسازی یا ذخیره وضعیت خاتمه می یابد -
Ignore
: فرآیند در پاسخ به سیگنال کاری انجام نمی دهد -
Catch
: فرآیند یک تابع کنترل کننده سیگنال تعریف شده توسط کاربر را برای کنترل سیگنال اجرا می کند
این بدان معنی است که نمی توان همه سیگنال ها را مدیریت کرد. برنامه شما فقط می تواند سیگنال هایی را که سیستم عامل به آن اجازه می دهد مدیریت کند. تمام سیگنال های از پیش تعریف شده ای که در بالا ذکر کردیم قابل کنترل هستند. با این حال، سیگنال های دیگری مانند SIGKILL
، SIGSTOP
، و SIGCONT
، که قابل رسیدگی نیست. مثلا، SIGKILL
برای خاتمه اجباری یک فرآیند استفاده می شود و نمی توان آن را گرفت، مسدود کرد یا نادیده گرفت.
کنترل سیگنال در Rust
اکنون که اصول اولیه سیگنال ها را پوشش دادیم، بیایید به دنیای کنترل سیگنال ها در Rust بپردازیم! برخلاف C، جایی که کنترل سیگنال در ماژولهای زبان تعبیه شده است، Rust چندین کتابخانه را فراهم میکند که توسعهدهندگان را قادر میسازد تا سیگنالها را به راحتی مدیریت کنند. کتابخانههایی مانند signal_hook، nix، libc و tokio سیگنالهایی را کنترل میکنند که عمدتاً از اتصالات C استفاده میکنند تا کار با سیگنالها را ممکن کنند.
کنترل سیگنال با توکیو
بیایید به مثالی نگاه کنیم تا نشان دهیم چگونه میتوانیم سیگنالها را در Rust با جعبه توکیو کنترل کنیم. جعبه سیگنال توکیو یک انتخاب عالی برای کنترل سیگنال است زیرا ناهمزمان و ایمن است. ضمناً از libc در پشت صحنه استفاده می کند.
ابتدا یک پروژه Rust با Cargo ایجاد کنید و با اجرای دستور زیر tokio را نصب کنید:
init && cargo add tokio
پس از اتمام نصب، آن را باز کنید cargo.toml
توکیو را فایل و فعال کنید full
ویژگی ها با به روز رسانی features
پرچم با full
استدلال مانند این: features=["full"]
. \
کد شما باید به شکل زیر باشد:
[package]
name = "practice"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version="1.25.0", features=["full"] }
سپس، بیایید یک کد نمونه برای مدیریت آن بنویسیم SIGINT
سیگنال – سیگنالی که هنگام فشار دادن فعال می شود Control+C
در برابر یک فرآیند در حال اجرا در ترمینال شما. این هم کد:
use tokio::signal::unix::{signal, SignalKind};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut sigint = signal(SignalKind::interrupt())?;
match sigint.recv().await {
Some(()) => println!("Received SIGINT signal"),
None => eprintln!("Stream terminated before receiving SIGINT signal"),
}
for num in 0..10000 {
println!("{}", num)
}
Ok(())
}
حالا، فرار کن cargo run
در ترمینال خود برای آزمایش کد. در حالی که کد در حال اجرا است، فشار دهید Control+C
، و پاسخی مانند این خواهید دید:
در کد بالا نوع سیگنال را با فراخوانی آن مقداردهی اولیه می کنیم signalKind
روش. SIGINT
تحت این عنوان مورد اشاره قرار گرفته است interrupt()
، و SIGTERM
تحت این عنوان مورد اشاره قرار گرفته است terminate()
. شما می توانید روش های دیگران را در اسناد پیدا کنید. در مورد ما، ما تماس می گیریم interrupt()
نوع:
let mut sigint = signal(SignalKind::interrupt())?;
پس از فراخوانی متد، شما آماده گوش دادن به آن سیگنال و مدیریت آن با استفاده از آن خواهید بود .recv()
روش، همانطور که در زیر نشان داده شده است:
match sigint.recv().await {
Some(()) => println!("Received SIGINT signal"),
None => eprintln!("Stream terminated before receiving SIGINT signal"),
}
این اساساً نحوه کنترل سیگنال ها در Rust تنها در چند خط کد است.
کاوش پوشش سیگنال در Rust
پوشش سیگنال فرآیندی است که به طور موقت ارسال سیگنال های خاص به یک فرآیند یا یک رشته را مسدود می کند. هنگامی که ماسک می شود، یک سیگنال به مجموعه ای از سیگنال های مسدود شده اضافه می شود و تا زمانی که مسدود نشده باشد، به فرآیند یا رشته تحویل داده نمی شود.
پوشش سیگنال اغلب برای جلوگیری از قطع شدن بخش های مهم کد که باید بدون وقفه توسط کنترل کننده سیگنال اجرا شوند، استفاده می شود. به عنوان مثال، در یک برنامه چند رشته ای، ممکن است یک بخش مهم از کد نیاز به اجرای اتمی داشته باشد بدون اینکه توسط یک کنترل کننده سیگنال قطع شود. در این حالت، برنامهنویس میتواند سیگنالهایی را که میتوانند بخش بحرانی را قطع کنند، موقتاً پوشانده و پس از تکمیل بخش بحرانی، آنها را از ماسک خارج کند.
مسدود کردن و رفع انسداد سیگنال ها با nix
بیایید به نمونه ای از نحوه مسدود کردن و رفع انسداد سیگنال ها با استفاده از جعبه nix نگاه کنیم. برای این مثال، از جعبه libc استفاده می کنیم. بنابراین، با نصب آن شروع کنید cargo add libc
در ترمینال شما سپس، این را به خود اضافه کنید src/main.rs file
:
use libc::{sigaddset, sigemptyset, sigprocmask, SIGINT, SIG_BLOCK, SIG_UNBLOCK};
use std::thread;
use std::time::Duration;
fn main() {
unsafe {
// Create an empty signal mask
let mut mask: libc::sigset_t = std::mem::zeroed();
sigemptyset(&mut mask);
// Add the SIGINT signal to the signal mask
sigaddset(&mut mask, SIGINT);
// Block the SIGINT signal using the signal mask
sigprocmask(SIG_BLOCK, &mask as *const libc::sigset_t, std::ptr::null_mut());
}
println!("Blocked SIGINT signal for 5 seconds");
thread::sleep(Duration::from_secs(5));
unsafe {
// Unblock the SIGINT signal using the signal mask
let mut mask: libc::sigset_t = std::mem::zeroed();
sigemptyset(&mut mask);
sigaddset(&mut mask, SIGINT);
sigprocmask(SIG_UNBLOCK, &mask as *const libc::sigset_t, std::ptr::null_mut());
}
println!("Unblocked SIGINT signal");
}
توجه داشته باشید که تابع را به عنوان علامت گذاری می کنیم unsafe
. ما این کار را انجام می دهیم زیرا شامل تعامل مستقیم با مکانیسم های مدیریت سیگنال سیستم عامل از طریق کتابخانه استاندارد C است. libc
رابط. همانطور که می بینید، ما در حال حذف ارجاع هستیم sigset_t
اشاره گر خام *const libc::sigset_t
زیرا آن قسمت از کد ناامن است.
در کد بالا، ما ارسال را مسدود می کنیم SIGINT
سیگنال تا بعد 5
ثانیه در درون آن ها 5
ثانیه، اگر فشار دهید Control+C
، هیچ اتفاقی نخواهد افتاد. با این حال SIGINT
سیگنال پس از راه اندازی خواهد شد 5
ثانیه می گذرد
اگر این کد را با cargo run
و فشار دهید Control+C
و همچنین بدون فشار دادن آن را اجرا کنید Control+C
، چیزی شبیه به این دریافت خواهید کرد: برای اولین فرمان اجرا شده در تصویر بالا، سیگنال بعد از آن اجرا شد
5
ثانیه چون فشار دادیم Control+C
. اما برای دومی اینطور نشد. همانطور که می بینید کد تا انتها اجرا شد.
نتیجه
کنترل سیگنال ها در Rust بسیار ساده است. علیرغم اسناد محدود موجود برای جعبههای سیگنال Rust، من اطمینان دارم که بینشهای ارائهشده در اینجا به عنوان نقطه شروع مفیدی برای پیادهسازی مدیریت سیگنال با استفاده از Rust خواهد بود.
هک مبارک!
LogRocket: دید کامل در صفحات وب برای برنامه های Rust
اشکال زدایی برنامه های Rust می تواند دشوار باشد، به خصوص زمانی که کاربران مشکلاتی را تجربه می کنند که بازتولید آن دشوار است. اگر به نظارت و ردیابی عملکرد برنامههای Rust، نمایش خودکار خطاها و پیگیری درخواستهای شبکه و زمان بارگذاری کند علاقه دارید، LogRocket را امتحان کنید.
LogRocket مانند یک DVR برای برنامه های وب است که به معنای واقعی کلمه هر چیزی را که در برنامه Rust شما اتفاق می افتد ضبط می کند. به جای حدس زدن چرایی مشکلات، می توانید در مورد وضعیتی که برنامه شما در هنگام بروز مشکل در آن قرار داشت، جمع آوری کرده و گزارش دهید. LogRocket همچنین بر عملکرد برنامه شما نظارت می کند، معیارهایی مانند بار CPU مشتری، استفاده از حافظه مشتری و موارد دیگر را گزارش می دهد.
نحوه اشکال زدایی برنامه های Rust خود را مدرن کنید – نظارت را به صورت رایگان شروع کنید.