Pemrograman C

Program C Pertama Anda Menggunakan Panggilan Sistem Fork

Program C Pertama Anda Menggunakan Panggilan Sistem Fork
Secara default, program C tidak memiliki konkurensi atau paralelisme, hanya satu tugas yang terjadi pada satu waktu, setiap baris kode dibaca secara berurutan. Tetapi terkadang, Anda harus membaca file atau - bahkan lebih buruk - soket yang terhubung ke komputer jarak jauh dan ini membutuhkan waktu yang sangat lama untuk komputer. Biasanya dibutuhkan kurang dari satu detik tetapi ingat bahwa satu inti CPU dapat mengeksekusi 1 atau 2 miliar instruksi selama waktu itu.

Begitu, sebagai pengembang yang baik, Anda akan tergoda untuk menginstruksikan program C Anda untuk melakukan sesuatu yang lebih berguna sambil menunggu. Di situlah pemrograman konkurensi ada di sini untuk menyelamatkan Anda - dan membuat komputer Anda tidak bahagia karena harus bekerja lebih banyak.

Di sini, saya akan menunjukkan kepada Anda panggilan sistem fork Linux, salah satu cara teraman untuk melakukan pemrograman bersamaan.

Pemrograman serentak bisa jadi tidak aman?

Ya, itu bisa. Misalnya, ada juga cara lain memanggil multithreading. Ada manfaatnya lebih ringan tapi bisa Betulkah salah jika Anda menggunakannya secara tidak benar. Jika program Anda, secara tidak sengaja, membaca variabel dan menulis ke to variabel yang sama pada saat yang sama, program Anda akan menjadi tidak koheren dan hampir tidak terdeteksi - salah satu mimpi buruk pengembang terburuk.

Seperti yang akan Anda lihat di bawah, garpu menyalin memori sehingga tidak mungkin memiliki masalah seperti itu dengan variabel. Juga, garpu membuat proses independen untuk setiap tugas bersamaan. Karena langkah-langkah keamanan ini, kira-kira 5x lebih lambat untuk meluncurkan tugas bersamaan yang baru menggunakan fork dibandingkan dengan multithreading. Seperti yang Anda lihat, itu tidak banyak untuk manfaat yang dibawanya.

Sekarang, cukup penjelasannya, saatnya untuk menguji program C pertama Anda menggunakan fork call.

Contoh garpu Linux Linux

Berikut kodenya:

#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
int utama()
pid_t forkStatus;
forkStatus = garpu();
/* Anak… */
if        (forkStatus == 0)
printf("Anak sedang berjalan, sedang memproses.\n");
tidur (5);
printf("Anak sudah selesai, keluar.\n");
/* Orang tua… */
else if (forkStatus != -1)
printf("Orang tua sedang menunggu... \n");
tunggu (NULL);
printf("Induk keluar... \n");
lain
perror("Error saat memanggil fungsi fork");

kembali 0;

Saya mengundang Anda untuk menguji, mengkompilasi, dan mengeksekusi kode di atas tetapi jika Anda ingin melihat seperti apa hasilnya dan Anda terlalu "malas" untuk mengompilasinya - lagi pula, Anda mungkin seorang pengembang lelah yang mengkompilasi program C sepanjang hari day - Anda dapat menemukan output dari program C di bawah ini bersama dengan perintah yang saya gunakan untuk mengkompilasinya:

$ gcc -std=c89 -Wpedantic -Wall forkSleep.c -o garpuTidur -O2
$ ./garpuTidur
Orang tua sedang menunggu…
Anak sedang berlari, memproses.
Anak selesai, keluar.
Orang tua keluar…

Tolong jangan takut jika outputnya tidak 100% identik dengan output saya di atas. Ingatlah bahwa menjalankan berbagai hal pada saat yang sama berarti tugas-tugas kehabisan pesanan, tidak ada pemesanan yang telah ditentukan sebelumnya. Dalam contoh ini, Anda mungkin melihat anak itu sedang berlari sebelum orang tua sedang menunggu, dan tidak ada yang salah dengan itu. Secara umum, urutannya tergantung pada versi kernel, jumlah inti CPU, program yang sedang berjalan di komputer Anda, dll.

Oke, sekarang kembali ke kode. Sebelum baris dengan fork(), program C ini sangat normal: 1 baris dieksekusi pada satu waktu, hanya ada satu proses untuk program ini (jika ada sedikit penundaan sebelum fork, Anda dapat mengonfirmasinya di pengelola tugas Anda).

Setelah fork(), sekarang ada 2 proses yang dapat berjalan secara paralel. Pertama, ada proses anak. Proses ini adalah proses yang telah dibuat pada fork(). Proses anak ini istimewa: ia belum mengeksekusi baris kode apa pun di atas baris dengan fork(). Alih-alih mencari fungsi utama, itu lebih baik menjalankan baris fork().

Bagaimana dengan variabel yang dideklarasikan sebelum fork?

Nah, fork Linux() menarik karena dengan cerdas menjawab pertanyaan ini. Variabel dan, pada kenyataannya, semua memori dalam program C disalin ke proses anak child.

Biarkan saya mendefinisikan apa yang melakukan garpu dalam beberapa kata: itu menciptakan a klon dari proses menyebutnya. 2 proses hampir identik: semua variabel akan berisi nilai yang sama dan kedua proses akan mengeksekusi baris tepat setelah fork(). Namun, setelah proses kloning, mereka terpisah. Jika Anda memperbarui variabel dalam satu proses, proses lainnya biasa perbarui variabelnya. Ini benar-benar tiruan, salinan, prosesnya hampir tidak berbagi apa-apa. Ini sangat berguna: Anda dapat menyiapkan banyak data dan kemudian fork() dan menggunakan data itu di semua klon.

Pemisahan dimulai ketika fork() mengembalikan nilai. Proses aslinya (disebut proses induk) akan mendapatkan ID proses dari proses kloning. Di sisi lain, proses kloning (yang ini disebut proses anak) akan mendapatkan angka 0. Sekarang, Anda harus mulai memahami mengapa saya meletakkan pernyataan if/else if setelah baris fork(). Menggunakan nilai balik, Anda dapat menginstruksikan anak untuk melakukan sesuatu yang berbeda dari apa yang dilakukan orang tua - dan percayalah, itu berguna.

Di satu sisi, pada contoh kode di atas, anak sedang mengerjakan tugas yang membutuhkan waktu 5 detik dan mencetak pesan. Untuk meniru proses yang memakan waktu lama, saya menggunakan fungsi tidur. Kemudian, anak itu berhasil keluar.

Di sisi lain, orang tua mencetak pesan, tunggu sampai anak keluar dan akhirnya mencetak pesan lain. Fakta orang tua menunggu anaknya itu penting. Sebagai contoh, orang tua menunggu sebagian besar waktu ini untuk menunggu anaknya. Tapi, saya bisa menginstruksikan orang tua untuk melakukan tugas jangka panjang apa pun sebelum menyuruhnya menunggu. Dengan cara ini, itu akan melakukan tugas yang berguna daripada menunggu - setelah semua, inilah mengapa kami menggunakan garpu(), tidak?

Namun, seperti yang saya katakan di atas, sangat penting bahwa orang tua menunggu anaknya. Dan itu penting karena proses zombie.

Betapa pentingnya menunggu

Orang tua umumnya ingin tahu apakah anak-anak telah menyelesaikan pemrosesan mereka. Misalnya, Anda ingin menjalankan tugas secara paralel tetapi kamu pasti tidak mau orang tua untuk keluar sebelum anak selesai, karena jika itu terjadi, shell akan memberikan kembali prompt sementara anak belum selesai - yang aneh.

Fungsi wait memungkinkan untuk menunggu sampai salah satu proses anak dihentikan. Jika orang tua memanggil 10 kali fork(), itu juga perlu memanggil 10 kali wait(), sekali untuk setiap anak dibuat.

Tetapi apa yang terjadi jika orang tua memanggil fungsi tunggu sementara semua anak memiliki sudah keluar? Di situlah proses zombie diperlukan.

Ketika seorang anak keluar sebelum orang tua memanggil wait(), kernel Linux akan membiarkan anak itu keluar tapi itu akan menyimpan tiket memberi tahu anak itu telah keluar. Kemudian, ketika orang tua memanggil wait(), ia akan menemukan tiket, menghapus tiket itu dan fungsi wait() akan kembali segera karena tahu orang tua perlu tahu kapan anak selesai. Tiket ini disebut proses zombie.

Itulah mengapa penting bagi induk untuk memanggil wait(): jika tidak, proses zombie tetap berada di memori dan kernel Linux tidak bisa simpan banyak proses zombie di memori. Setelah batas tercapai, komputer Anda is tidak dapat membuat proses baru dan Anda akan berada di bentuk yang sangat buruk: bahkan untuk mematikan suatu proses, Anda mungkin perlu membuat proses baru untuk itu. Misalnya, jika Anda ingin membuka pengelola tugas untuk mematikan proses, Anda tidak bisa, karena pengelola tugas Anda akan memerlukan proses baru. Bahkan lebih buruk, kamu tidak bisa membunuh proses zombie.

Itu sebabnya memanggil wait itu penting: memungkinkan kernel membersihkan proses anak alih-alih terus menumpuk dengan daftar proses yang dihentikan. Dan bagaimana jika orang tua keluar tanpa pernah menelepon? Tunggu()?

Untungnya, saat induk dihentikan, tidak ada orang lain yang dapat memanggil wait() untuk anak-anak ini, jadi ada tak ada alasan untuk menjaga proses zombie ini. Karena itu, ketika orang tua keluar, semua tersisa proses zombie terkait dengan orang tua ini dihapus. Proses zombie adalah Betulkah hanya berguna untuk mengizinkan proses induk menemukan bahwa anak dihentikan sebelum induk disebut wait().

Sekarang, Anda mungkin lebih suka mengetahui beberapa langkah keamanan untuk memungkinkan Anda menggunakan garpu terbaik tanpa masalah.

Aturan sederhana agar garpu berfungsi sebagaimana dimaksud

Pertama, jika Anda tahu multithreading, jangan memotong program menggunakan utas. Bahkan, hindari secara umum untuk menggabungkan beberapa teknologi konkurensi. garpu mengasumsikan untuk bekerja dalam program C normal, itu hanya bermaksud untuk mengkloning satu tugas paralel, tidak lebih.

Kedua, hindari membuka atau membuka file sebelum fork(). File adalah satu-satunya hal bersama dan tidak kloning antara orang tua dan anak. Jika Anda membaca 16 byte pada induknya, itu akan memindahkan kursor baca ke depan sebesar 16 byte kedua pada orang tua dan pada anak. Terburuk, jika anak dan orang tua menulis byte ke file yang sama pada saat yang sama, byte induk dapat Campuran dengan byte anak!

Agar jelas, di luar  STDIN, STDOUT, & STDERR, Anda benar-benar tidak ingin membagikan file apa pun yang terbuka dengan kloning.

Ketiga, berhati-hatilah dengan soket. Soket adalah juga berbagi antara orang tua dan anak. Ini berguna untuk mendengarkan port dan kemudian membiarkan beberapa pekerja anak siap untuk menangani koneksi klien baru. Namun, jika Anda menggunakannya salah, Anda akan mendapat masalah.

Keempat, jika Anda ingin memanggil fork() dalam satu lingkaran, lakukan ini dengan sangat hati-hati. Mari kita ambil kode ini:

/* JANGAN KOMPLAIN INI */
const int targetFork = 4;
pid_t forkHasil
 
untuk (int i = 0; i < targetFork; i++)
hasil fork = garpu();
/*… */
 

Jika Anda membaca kodenya, Anda mungkin mengharapkannya untuk membuat 4 anak. Tapi itu akan lebih membuat 16 anak. Itu karena anak-anak akan juga jalankan loop dan anak-anak akan, pada gilirannya, memanggil fork(). Ketika loop tidak terbatas, itu disebut a bom garpu dan merupakan salah satu cara untuk memperlambat sistem Linux sedemikian rupa sehingga tidak lagi berfungsi dan akan membutuhkan reboot. Singkatnya, perlu diingat bahwa Clone Wars tidak hanya berbahaya di Star Wars!

Sekarang Anda telah melihat bagaimana loop sederhana bisa salah, cara menggunakan loop dengan fork()? Jika Anda membutuhkan loop, selalu periksa nilai pengembalian fork:

const int targetFork = 4;
pid_t forkResult;
int saya = 0;
lakukan
hasil fork = garpu();
/*… */
saya++;
while ((forkResult != 0 && forkResult != -1) && (i < targetFork));

Kesimpulan

Sekarang saatnya Anda melakukan eksperimen sendiri dengan fork()! Coba cara baru untuk mengoptimalkan waktu dengan melakukan tugas di beberapa inti CPU atau melakukan pemrosesan latar belakang saat Anda menunggu untuk membaca file!

Jangan ragu untuk membaca halaman manual melalui perintah man. Anda akan belajar tentang bagaimana fork() bekerja dengan tepat, kesalahan apa yang bisa Anda dapatkan, dll. Dan nikmati konkurensi!

Cara Menggunakan Cheat Engine GameConqueror di Linux
Artikel ini mencakup panduan tentang menggunakan mesin cheat GameConqueror di Linux. Banyak pengguna yang bermain game di Windows sering menggunakan a...
Emulator Konsol Game Terbaik untuk Linux
Artikel ini akan mencantumkan perangkat lunak emulasi konsol game populer yang tersedia untuk Linux. Emulasi adalah lapisan kompatibilitas perangkat l...
Distro Linux Terbaik untuk Gaming pada tahun 2021
Sistem operasi Linux telah berkembang jauh dari tampilan aslinya, sederhana, berbasis server. OS ini telah sangat meningkat dalam beberapa tahun terak...