Tutorial Panggilan Sistem Linux dengan C

Linux System Call Tutorial With C



Dalam artikel terakhir kami di Panggilan Sistem Linux , saya mendefinisikan panggilan sistem, membahas alasan seseorang menggunakannya dalam suatu program, dan mempelajari kelebihan dan kekurangannya. Saya bahkan memberikan contoh singkat dalam perakitan dalam C. Ini menggambarkan poin dan menjelaskan bagaimana melakukan panggilan, tetapi tidak menghasilkan apa-apa. Bukan latihan pengembangan yang mendebarkan, tetapi itu menggambarkan intinya.

Pada artikel ini, kita akan menggunakan panggilan sistem yang sebenarnya untuk melakukan pekerjaan nyata dalam program C kita. Pertama, kami akan meninjau jika Anda perlu menggunakan panggilan sistem, kemudian memberikan contoh menggunakan panggilan sendfile() yang dapat meningkatkan kinerja penyalinan file secara dramatis. Terakhir, kita akan membahas beberapa hal yang perlu diingat saat menggunakan panggilan sistem Linux.







Meskipun tidak dapat dihindari, Anda akan menggunakan panggilan sistem di beberapa titik dalam karir pengembangan C Anda, kecuali jika Anda menargetkan kinerja tinggi atau fungsionalitas tipe tertentu, pustaka glibc dan pustaka dasar lainnya yang termasuk dalam distribusi Linux utama akan menangani sebagian besar kebutuhanmu.



Pustaka standar glibc menyediakan kerangka kerja lintas platform yang telah teruji dengan baik untuk menjalankan fungsi yang memerlukan panggilan sistem khusus sistem. Misalnya, Anda dapat membaca file dengan fscanf(), fread(), getc(), dll., atau Anda dapat menggunakan read() panggilan sistem Linux. Fungsi glibc menyediakan lebih banyak fitur (yaitu penanganan kesalahan yang lebih baik, IO yang diformat, dll.) dan akan bekerja pada sistem apa pun yang didukung glibc.



Di sisi lain, ada kalanya kinerja tanpa kompromi dan eksekusi yang tepat sangat penting. Pembungkus yang disediakan fread() akan menambah overhead, dan meskipun kecil, tidak sepenuhnya transparan. Selain itu, Anda mungkin tidak menginginkan atau membutuhkan fitur tambahan yang disediakan pembungkus. Dalam hal ini, Anda paling baik dilayani dengan panggilan sistem.





Anda juga dapat menggunakan panggilan sistem untuk menjalankan fungsi yang belum didukung oleh glibc. Jika salinan glibc Anda mutakhir, ini tidak akan menjadi masalah, tetapi mengembangkan distro lama dengan kernel yang lebih baru mungkin memerlukan teknik ini.

Sekarang setelah Anda membaca penafian, peringatan, dan kemungkinan jalan memutar, sekarang mari kita gali beberapa contoh praktis.



CPU apa yang kita pakai?

Sebuah pertanyaan yang mungkin tidak terpikirkan oleh sebagian besar program, tetapi tetap valid. Ini adalah contoh panggilan sistem yang tidak dapat diduplikasi dengan glibc dan tidak ditutupi dengan pembungkus glibc. Dalam kode ini, kita akan memanggil panggilan getcpu() secara langsung melalui fungsi syscall(). Fungsi syscall bekerja sebagai berikut:

panggilan sistem(SYS_call,arg1,arg2,...);

Argumen pertama, SYS_call, adalah definisi yang mewakili nomor panggilan sistem. Saat Anda menyertakan sys/syscall.h, ini disertakan. Bagian pertama adalah SYS_ dan bagian kedua adalah nama panggilan sistem.

Argumen untuk panggilan masuk ke arg1, arg2 di atas. Beberapa panggilan memerlukan lebih banyak argumen, dan mereka akan melanjutkan secara berurutan dari halaman manual mereka. Ingat bahwa sebagian besar argumen, terutama untuk pengembalian, akan memerlukan pointer ke array char atau memori yang dialokasikan melalui fungsi malloc.

contoh1.c

#termasuk
#termasuk
#termasuk
#termasuk

ke dalamutama() {

tidak ditandatanganicpu,simpul;

// Dapatkan inti CPU dan simpul NUMA saat ini melalui panggilan sistem
// Perhatikan ini tidak memiliki pembungkus glibc jadi kita harus memanggilnya secara langsung
panggilan sistem(SYS_getcpu, &cpu, &simpul,BATAL);

// Menampilkan informasi
printf ('Program ini berjalan pada inti CPU %u dan NUMA node %u. ',cpu,simpul);

kembali 0;

}

Untuk mengkompilasi dan menjalankan:

contoh gcc1.C -o contoh1
./Contoh 1

Untuk hasil yang lebih menarik, Anda dapat memutar utas melalui pustaka pthreads dan kemudian memanggil fungsi ini untuk melihat prosesor mana yang menjalankan utas Anda.

Sendfile: Performa Unggul

Sendfile memberikan contoh yang sangat baik untuk meningkatkan kinerja melalui panggilan sistem. Fungsi sendfile() menyalin data dari satu deskriptor file ke deskriptor file lainnya. Daripada menggunakan beberapa fungsi fread() dan fwrite(), sendfile melakukan transfer di ruang kernel, mengurangi overhead dan dengan demikian meningkatkan kinerja.

Dalam contoh ini, kita akan menyalin 64 MB data dari satu file ke file lainnya. Dalam satu pengujian, kita akan menggunakan metode baca/tulis standar di pustaka standar. Di sisi lain, kami akan menggunakan panggilan sistem dan panggilan sendfile() untuk meledakkan data ini dari satu lokasi ke lokasi lain.

tes1.c (glibc)

#termasuk
#termasuk
#termasuk
#termasuk

#tentukan BUFFER_SIZE 67108864
#define BUFFER_1 'buffer1'
#define BUFFER_2 'buffer2'

ke dalamutama() {

MENGAJUKAN*salah, *akhir;

printf (' Tes I/O dengan fungsi glibc tradisional. ');

// Ambil buffer BUFFER_SIZE.
// Buffer akan memiliki data acak di dalamnya tapi kami tidak peduli tentang itu.
printf ('Mengalokasikan 64 MB buffer:');
arang *penyangga= (arang *) malloc (UKURAN BUFFER);
printf ('SELESAI ');

// Tulis buffer ke fOut
printf ('Menulis data ke buffer pertama:');
salah= fopen (BUFFER_1, 'wb');
menulis (penyangga, ukuran dari(arang),UKURAN BUFFER,salah);
tutup (salah);
printf ('SELESAI ');

printf ('Menyalin data dari file pertama ke kedua:');
akhir= fopen (BUFFER_1, 'rb');
salah= fopen (BUFFER_2, 'wb');
ketakutan (penyangga, ukuran dari(arang),UKURAN BUFFER,akhir);
menulis (penyangga, ukuran dari(arang),UKURAN BUFFER,salah);
tutup (akhir);
tutup (salah);
printf ('SELESAI ');

printf ('Membebaskan penyangga:');
Gratis (penyangga);
printf ('SELESAI ');

printf ('Menghapus file:');
menghapus (BUFFER_1);
menghapus (BUFFER_2);
printf ('SELESAI ');

kembali 0;

}

test2.c (panggilan sistem)

#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
#termasuk

#tentukan BUFFER_SIZE 67108864

ke dalamutama() {

ke dalamsalah,akhir;

printf (' Tes I/O dengan sendfile() dan panggilan sistem terkait. ');

// Ambil buffer BUFFER_SIZE.
// Buffer akan memiliki data acak di dalamnya tapi kami tidak peduli tentang itu.
printf ('Mengalokasikan 64 MB buffer:');
arang *penyangga= (arang *) malloc (UKURAN BUFFER);
printf ('SELESAI ');


// Tulis buffer ke fOut
printf ('Menulis data ke buffer pertama:');
salah=membuka('penyangga1',O_RDONLY);
menulis(salah, &penyangga,UKURAN BUFFER);
Menutup(salah);
printf ('SELESAI ');

printf ('Menyalin data dari file pertama ke kedua:');
akhir=membuka('penyangga1',O_RDONLY);
salah=membuka('penyangga2',O_RDONLY);
kirim file(salah,akhir, 0,UKURAN BUFFER);
Menutup(akhir);
Menutup(salah);
printf ('SELESAI ');

printf ('Membebaskan penyangga:');
Gratis (penyangga);
printf ('SELESAI ');

printf ('Menghapus file:');
putuskan tautan('penyangga1');
putuskan tautan('penyangga2');
printf ('SELESAI ');

kembali 0;

}

Mengkompilasi dan Menjalankan Tes 1 & 2

Untuk membangun contoh-contoh ini, Anda memerlukan alat pengembangan yang diinstal pada distribusi Anda. Di Debian dan Ubuntu, Anda dapat menginstal ini dengan:

tepatInstallmembangun-penting

Kemudian kompilasi dengan:

gcctes1.c-atautes1&& gcctes2.c-atautes2

Untuk menjalankan keduanya dan menguji kinerja, jalankan:

waktu./tes1&& waktu./tes2

Anda harus mendapatkan hasil seperti ini:

Tes I/O dengan fungsi glibc tradisional.

Mengalokasikan 64 MB buffer: SELESAI
Menulis data ke buffer pertama: SELESAI
Menyalin data dari file pertama ke kedua: SELESAI
Membebaskan buffer: SELESAI
Menghapus file: SELESAI
nyata 0m0.397s
pengguna 0m0.000s
sys 0m0.203s
Tes I/O dengan sendfile() dan panggilan sistem terkait.
Mengalokasikan 64 MB buffer: SELESAI
Menulis data ke buffer pertama: SELESAI
Menyalin data dari file pertama ke kedua: SELESAI
Membebaskan buffer: SELESAI
Menghapus file: SELESAI
nyata 0m0.019s
pengguna 0m0.000s
sys 0m0.016s

Seperti yang Anda lihat, kode yang menggunakan panggilan sistem berjalan jauh lebih cepat daripada yang setara dengan glibc.

Hal-hal untuk diingat

Panggilan sistem dapat meningkatkan kinerja dan menyediakan fungsionalitas tambahan, tetapi bukan tanpa kekurangannya. Anda harus mempertimbangkan manfaat yang diberikan panggilan sistem terhadap kurangnya portabilitas platform dan terkadang fungsionalitas yang berkurang dibandingkan dengan fungsi perpustakaan.

Saat menggunakan beberapa panggilan sistem, Anda harus berhati-hati untuk menggunakan sumber daya yang dikembalikan dari panggilan sistem daripada fungsi perpustakaan. Misalnya, struktur FILE yang digunakan untuk fungsi fopen(), fread(), fwrite(), dan fclose() glibc tidak sama dengan nomor deskriptor file dari panggilan sistem open() (dikembalikan sebagai bilangan bulat). Mencampur ini dapat menyebabkan masalah.

Secara umum, panggilan sistem Linux memiliki lebih sedikit jalur bumper daripada fungsi glibc. Meskipun benar bahwa panggilan sistem memiliki beberapa penanganan dan pelaporan kesalahan, Anda akan mendapatkan fungsionalitas yang lebih detail dari fungsi glibc.

Dan akhirnya, kata tentang keamanan. Panggilan sistem langsung berinteraksi dengan kernel. Kernel Linux memang memiliki perlindungan ekstensif terhadap kejahatan dari tanah pengguna, tetapi ada bug yang belum ditemukan. Jangan percaya bahwa panggilan sistem akan memvalidasi masukan Anda atau mengisolasi Anda dari masalah keamanan. Adalah bijaksana untuk memastikan data yang Anda berikan ke panggilan sistem dibersihkan. Secara alami, ini adalah saran yang bagus untuk panggilan API apa pun, tetapi Anda tidak boleh berhati-hati saat bekerja dengan kernel.

Saya harap Anda menikmati penyelaman lebih dalam ke tanah panggilan sistem Linux ini. Untuk daftar lengkap Panggilan Sistem Linux, lihat daftar utama kami.