Pemrograman C

Baca Syscall Linux

Baca Syscall Linux
Jadi, Anda perlu membaca data biner? Anda mungkin ingin membaca dari FIFO atau soket? Anda lihat, Anda dapat menggunakan fungsi pustaka standar C, tetapi dengan melakukannya, Anda tidak akan mendapat manfaat dari fitur khusus yang disediakan oleh Kernel Linux dan POSIX. Misalnya, Anda mungkin ingin menggunakan batas waktu untuk membaca pada waktu tertentu tanpa menggunakan polling. Selain itu, Anda mungkin perlu membaca sesuatu tanpa peduli apakah itu file atau soket khusus atau yang lainnya. Satu-satunya tugas Anda adalah membaca beberapa konten biner dan mendapatkannya di aplikasi Anda. Di situlah syscall baca bersinar.

Baca file normal dengan syscall Linux

Cara terbaik untuk mulai bekerja dengan fungsi ini adalah dengan membaca file biasa. Ini adalah cara paling sederhana untuk menggunakan syscall itu, dan karena suatu alasan: tidak memiliki kendala sebanyak jenis aliran atau pipa lainnya. Jika Anda memikirkannya, itulah logikanya, ketika Anda membaca output dari aplikasi lain, Anda perlu menyiapkan beberapa output sebelum membacanya dan karenanya Anda perlu menunggu aplikasi ini untuk menulis output ini.

Pertama, perbedaan utama dengan perpustakaan standar: Tidak ada buffering sama sekali. Setiap kali Anda memanggil fungsi baca, Anda akan memanggil Kernel Linux, jadi ini akan memakan waktu -‌ hampir instan jika Anda memanggilnya sekali, tetapi dapat memperlambat Anda jika Anda memanggilnya ribuan kali dalam satu detik. Sebagai perbandingan, perpustakaan standar akan menyangga input untuk Anda. Jadi setiap kali Anda memanggil read, Anda harus membaca lebih dari beberapa byte, melainkan buffer besar seperti beberapa kilobytebyte - kecuali jika yang Anda butuhkan benar-benar sedikit byte, misalnya jika Anda memeriksa apakah ada file dan tidak kosong.

Namun ini memiliki manfaat: setiap kali Anda memanggil baca, Anda yakin Anda mendapatkan data yang diperbarui, jika ada aplikasi lain yang memodifikasi file saat ini. Ini sangat berguna untuk file khusus seperti yang ada di /proc atau /sys.

Saatnya menunjukkan kepada Anda dengan contoh nyata. Program C ini memeriksa apakah file tersebut PNG atau tidak. Untuk melakukannya, ia membaca file yang ditentukan di jalur yang Anda berikan dalam argumen baris perintah, dan memeriksa apakah 8 byte pertama sesuai dengan header PNG.

Berikut kodenya:

#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
 
typedef enum
IS_PNG,
TERLALU SINGKAT,
INVALID_HEADER
pngStatus_t;
 
unsigned int isSyscallSuccessful(const ssize_t readStatus)
kembali readStatus >= 0;
 

 
/*
* checkPngHeader sedang memeriksa apakah array pngFileHeader sesuai dengan PNG
* tajuk file.
*
* Saat ini hanya memeriksa 8 byte pertama dari array. Jika array lebih kecil
* dari 8 byte, TOO_SHORT dikembalikan.
*
* pngFileHeaderLength harus sesuai dengan kength of tye array. Nilai yang tidak valid
* dapat menyebabkan perilaku yang tidak terdefinisi, seperti aplikasi mogok.
*
* Mengembalikan IS_PNG jika sesuai dengan header file PNG. Jika ada setidaknya
* 8 byte dalam array tetapi bukan header PNG, INVALID_HEADER dikembalikan.
*
*/
pngStatus_t checkPngHeader(const unsigned char* const pngFileHeader,
size_t pngFileHeaderLength) const unsigned char yang diharapkanPngHeader[8] =
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A;
int saya = 0;
 
jika (pngFileHeaderLength < sizeof(expectedPngHeader))
kembali TOO_SHORT;
 

 
untuk (i = 0; i < sizeof(expectedPngHeader); i++)
jika (pngFileHeader[i] != diharapkanPngHeader[i])
kembali INVALID_HEADER;
 


 
/* Jika sampai di sini, semua 8 byte pertama sesuai dengan header PNG. */
kembalikan IS_PNG;

 
int main(int argumentLength,  char *argumentList[])
char *pngFileName = NULL;
karakter yang tidak ditandatangani pngFileHeader[8] = 0;
 
ssize_t readStatus = 0;
/* Linux menggunakan nomor untuk mengidentifikasi file yang terbuka. */
int pngFile = 0;
pngStatus_t pngCheckResult;
 
jika (panjang argumen != 2)
fputs("Anda harus memanggil program ini menggunakan isPng nama file Anda.\n", stderr);
kembali EXIT_FAILURE;
 

 
pngFileName = argumenList[1];
pngFile = buka(pngFileName, O_RDONLY);
 
if (file png == -1)
perror("Gagal membuka file yang disediakan");
kembali EXIT_FAILURE;
 

 
/* Baca beberapa byte untuk mengidentifikasi apakah file tersebut PNG. */
readStatus = baca(pngFile, pngFileHeader, sizeof(pngFileHeader));
 
if (isSyscallSuccessful(readStatus))
/* Periksa apakah file tersebut adalah PNG karena mendapatkan datanya. */
pngCheckResult = checkPngHeader(pngFileHeader, readStatus);
 
if        (pngCheckResult == TOO_SHORT)
printf("File %s bukan file PNG: terlalu pendek.\n", pngFileName);
 
else if (pngCheckResult == IS_PNG)
printf("File %s adalah file PNG!\n", pngFileName);
 
lain
printf("File %s tidak dalam format PNG.\n", pngFileName);
 

 
lain
perror("Gagal Membaca File");
kembali EXIT_FAILURE;
 

 
/* Tutup file… */
if (tutup(pngFile) == -1)
perror("Gagal menutup file yang disediakan");
kembali EXIT_FAILURE;
 

 
pngFile = 0;
 
kembali EXIT_SUCCESS;
 

Lihat, ini adalah contoh yang lengkap, berfungsi, dan dapat dikompilasi. Jangan ragu untuk mengkompilasinya sendiri dan mengujinya, itu benar-benar berfungsi. Anda harus memanggil program dari terminal seperti ini:

./isPng nama file Anda

Sekarang, mari kita fokus pada panggilan baca itu sendiri:

pngFile = buka(pngFileName, O_RDONLY);
if (file png == -1)
perror("Gagal membuka file yang disediakan");
kembali EXIT_FAILURE;

/* Baca beberapa byte untuk mengidentifikasi apakah file tersebut PNG. */
readStatus = baca(pngFile, pngFileHeader, sizeof(pngFileHeader));

Tanda tangan baca adalah sebagai berikut (diekstrak dari halaman manual Linux):

ssize_t read(int fd, void *buf, size_t count);

Pertama, argumen fd mewakili deskriptor file. Saya telah menjelaskan sedikit konsep ini di artikel garpu saya my.  Deskriptor file adalah int yang mewakili file terbuka, soket, pipa, FIFO, perangkat, nah itu banyak hal di mana data dapat dibaca atau ditulis, umumnya dengan cara seperti aliran. Saya akan membahas lebih dalam tentang itu di artikel mendatang.

fungsi buka adalah salah satu cara untuk memberitahu ke Linux: Saya ingin melakukan sesuatu dengan file di jalur itu, tolong temukan di mana itu dan beri saya akses ke sana. Ini akan memberi Anda kembali int yang disebut deskriptor file dan sekarang, jika Anda ingin melakukan sesuatu dengan file ini, gunakan nomor itu. Jangan lupa untuk memanggil tutup ketika Anda selesai dengan file, seperti pada contoh.

Jadi, Anda perlu memberikan nomor khusus ini untuk dibaca. Lalu ada argumen buff. Anda di sini harus memberikan pointer ke array tempat read akan menyimpan data Anda. Akhirnya, count adalah berapa banyak byte yang paling banyak dibaca read.

Nilai yang dikembalikan adalah tipe ssize_t. Tipe yang aneh bukan? Itu berarti “ukuran_t yang ditandatangani”, pada dasarnya ini adalah int yang panjang. Ini mengembalikan jumlah byte yang berhasil dibaca, atau -1 jika ada masalah. Anda dapat menemukan penyebab pasti masalah dalam variabel global errno yang dibuat oleh Linux, yang didefinisikan dalam . Tetapi untuk mencetak pesan kesalahan, menggunakan perror lebih baik karena mencetak errno atas nama Anda.

Dalam file normal - dan hanya dalam hal ini - read akan mengembalikan kurang dari hitungan hanya jika Anda telah mencapai akhir file. Array buf yang Anda berikan harus cukup besar untuk memuat setidaknya hitungan byte, atau program Anda mungkin macet atau membuat bug keamanan.

Sekarang, membaca tidak hanya berguna untuk file biasa dan jika Anda ingin merasakan kekuatan supernya - Ya, saya tahu itu tidak ada di komik Marvel mana pun tetapi memiliki kekuatan sejati - Anda akan ingin menggunakannya dengan aliran lain seperti pipa atau soket. Mari kita lihat itu:

File khusus Linux dan baca panggilan sistem

Fakta membaca bekerja dengan berbagai file seperti pipa, soket, FIFO atau perangkat khusus seperti disk atau port serial adalah apa yang membuatnya benar-benar lebih kuat. Dengan beberapa adaptasi, Anda dapat melakukan hal-hal yang sangat menarik. Pertama, ini berarti Anda benar-benar dapat menulis fungsi yang bekerja pada file dan menggunakannya dengan pipa sebagai gantinya. Itu menarik untuk melewatkan data tanpa pernah memukul disk, memastikan kinerja terbaik.

Namun ini memicu aturan khusus juga. Mari kita ambil contoh pembacaan baris dari terminal dibandingkan dengan file normal. Saat Anda memanggil read pada file normal, hanya perlu beberapa milidetik ke Linux untuk mendapatkan jumlah data yang Anda minta.

Tetapi ketika datang ke terminal, itu cerita lain: katakanlah Anda meminta nama pengguna. Pengguna mengetik di terminal nama pengguna dan tekan Enter. Sekarang Anda mengikuti saran saya di atas dan Anda memanggil read dengan buffer besar seperti 256 byte.

Jika read berfungsi seperti halnya dengan file, itu akan menunggu pengguna mengetik 256 karakter sebelum kembali! Pengguna Anda akan menunggu selamanya, dan kemudian dengan sedih mematikan aplikasi Anda. Ini tentu bukan yang Anda inginkan, dan Anda akan memiliki masalah besar.

Oke, Anda bisa membaca satu byte pada satu waktu tetapi solusi ini sangat tidak efisien, seperti yang saya katakan di atas. Itu harus bekerja lebih baik dari itu.

Tetapi pengembang Linux berpikir membaca secara berbeda untuk menghindari masalah ini:

  • Saat Anda membaca file normal, ia mencoba sebanyak mungkin untuk membaca jumlah byte dan secara aktif akan mendapatkan byte dari disk jika diperlukan.
  • Untuk semua jenis file lainnya, itu akan kembali sesegera ada beberapa data yang tersedia dan paling banyak menghitung byte:
    1. Untuk terminal, itu umumnya ketika pengguna menekan tombol Enter.
    2. Untuk soket TCP, segera setelah komputer Anda menerima sesuatu, tidak peduli jumlah byte yang didapat.
    3. Untuk FIFO atau pipa, umumnya jumlah yang sama seperti yang ditulis oleh aplikasi lain, tetapi kernel Linux dapat mengirimkan lebih sedikit pada satu waktu jika itu lebih nyaman.

Jadi Anda dapat menelepon dengan aman menggunakan buffer 2 KiB tanpa terkunci selamanya. Perhatikan itu juga bisa terganggu jika aplikasi menerima sinyal. Karena membaca dari semua sumber ini bisa memakan waktu beberapa detik atau bahkan berjam-jam - sampai pihak lain memutuskan untuk menulis - terganggu oleh sinyal memungkinkan untuk berhenti diblokir terlalu lama.

Ini juga memiliki kelemahan: ketika Anda ingin membaca 2 KiB dengan tepat dengan file khusus ini, Anda harus memeriksa nilai kembalian read dan call read beberapa kali. membaca jarang akan mengisi seluruh buffer Anda. Jika aplikasi Anda menggunakan sinyal, Anda juga perlu memeriksa apakah pembacaan gagal dengan -1 karena terputus oleh sinyal, menggunakan errno.

Mari saya tunjukkan bagaimana menarik untuk menggunakan properti khusus dari read:

#define _POSIX_C_SOURCE 1 /* sigaction tidak tersedia tanpa #define ini. */
#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
#termasuk
/*
* isSignal memberitahu jika panggilan syscall telah terganggu oleh sinyal.
*
* Mengembalikan TRUE jika panggilan syscall telah terganggu oleh sinyal.
*
* Variabel global: terbaca errno yang didefinisikan dalam errno.h
*/
unsigned int isSignal(const ssize_t readStatus)
kembali (readStatus == -1 && errno == EINTR);

unsigned int isSyscallSuccessful(const ssize_t readStatus)
kembali readStatus >= 0;

/*
* shouldRestartRead memberitahu ketika syscall membaca telah terganggu oleh
* peristiwa sinyal atau tidak, dan mengingat alasan "kesalahan" ini bersifat sementara, kami dapat
* restart panggilan baca dengan aman.
*
* Saat ini, ini hanya memeriksa apakah pembacaan telah terganggu oleh sinyal, tetapi itu
* dapat ditingkatkan untuk memeriksa apakah jumlah byte target telah dibaca dan apakah itu
*tidak demikian, kembalikan TRUE untuk membaca lagi.
*
*/
unsigned int shouldRestartRead(const ssize_t readStatus)
return isSignal(readStatus);

/*
* Kami membutuhkan penangan kosong karena panggilan sistem baca akan terputus hanya jika
* sinyal ditangani.
*/
void emptyHandler(int diabaikan)
kembali;

int utama()
/* Dalam hitungan detik. */
const int alarmInterval = 5;
const struct sigaction emptySigaction = emptyHandler;
char lineBuf[256] = 0;
ssize_t readStatus = 0;
waktu tunggu int yang tidak ditandatangani = 0;
/* Jangan memodifikasi sigaction kecuali jika Anda tahu persis apa yang Anda lakukan. */
sigaction(SIGALRM, &emptySigaction, NULL);
alarm(alarmInterval);
fputs("Teks Anda:\n", stderr);
lakukan
/* Jangan lupa '\0' */
readStatus = read(STDIN_FILENO, lineBuf, sizeof(lineBuf) - 1);
if (isSignal(readStatus))
waktu tunggu += alarmInterval;
alarm(alarmInterval);
fprintf(stderr, "%u detik tidak aktif… \n", waktu tunggu);

while (shouldRestartRead(readStatus));
if (isSyscallSuccessful(readStatus))
/* Hentikan string untuk menghindari bug saat memberikannya ke fprintf. */
lineBuf[readStatus] = '\0';
fprintf(stderr, "Anda mengetik %lu karakter. Ini string Anda:\n%s\n", strlen(lineBuf),
barisBuf);
lain
perror("Membaca dari stdin gagal");
kembali EXIT_FAILURE;

kembali EXIT_SUCCESS;

Sekali lagi, ini adalah aplikasi C lengkap yang dapat Anda kompilasi dan jalankan.

Ia melakukan hal berikut: membaca baris dari input standar. Namun, setiap 5 detik, ia mencetak baris yang memberi tahu pengguna bahwa belum ada input yang diberikan.

Contoh jika saya menunggu 23 detik sebelum mengetik “Penguin”:

$ alarm_read
Teks Anda:
5 detik tidak aktif…
10 detik tidak aktif…
15 detik tidak aktif…
20 detik tidak aktif…
pinguin
Anda mengetik 8 karakter. Inilah string Anda:
pinguin

Itu sangat berguna. Ini dapat digunakan untuk sering memperbarui UI untuk mencetak kemajuan pembacaan atau pemrosesan aplikasi yang Anda lakukan. Ini juga dapat digunakan sebagai mekanisme batas waktu. Anda juga bisa terganggu oleh sinyal lain yang mungkin berguna untuk aplikasi Anda. Bagaimanapun, ini berarti aplikasi Anda sekarang dapat responsif alih-alih tetap macet selamanya.

Jadi manfaatnya lebih besar daripada kerugian yang dijelaskan di atas. Jika Anda bertanya-tanya apakah Anda harus mendukung file khusus dalam aplikasi yang biasanya bekerja dengan file normal - dan begitu memanggil Baca dalam satu lingkaran - Saya akan mengatakan melakukannya kecuali jika Anda sedang terburu-buru, pengalaman pribadi saya sering membuktikan bahwa mengganti file dengan pipa atau FIFO benar-benar dapat membuat aplikasi jauh lebih berguna dengan upaya kecil. Bahkan ada fungsi C yang dibuat sebelumnya di Internet yang mengimplementasikan loop itu untuk Anda: itu disebut fungsi readn.

Kesimpulan

Seperti yang Anda lihat, fread dan read mungkin terlihat mirip, mereka tidak. Dan dengan hanya sedikit perubahan pada cara kerja read untuk pengembang C, read jauh lebih menarik untuk merancang solusi baru untuk masalah yang Anda temui selama pengembangan aplikasi.

Lain kali, saya akan memberi tahu Anda bagaimana menulis syscall bekerja, karena membaca itu keren, tetapi bisa melakukan keduanya jauh lebih baik. Sementara itu, bereksperimenlah dengan membaca, kenali dan saya ucapkan Selamat Tahun Baru!

Cara mengubah penunjuk Mouse dan ukuran kursor, warna & skema pada Windows 10
Penunjuk mouse dan kursor di Windows 10 adalah aspek yang sangat penting dari sistem operasi. Ini dapat dikatakan untuk sistem operasi lain juga, jadi...
Mesin Game Gratis dan Sumber Terbuka untuk Mengembangkan Game Linux
Artikel ini akan membahas daftar mesin game sumber terbuka dan gratis yang dapat digunakan untuk mengembangkan game 2D dan 3D di Linux. Ada banyak mes...
Shadow of the Tomb Raider untuk Tutorial Linux
Shadow of the Tomb Raider adalah tambahan kedua belas untuk seri Tomb Raider - waralaba game aksi-petualangan yang dibuat oleh Eidos Montreal. Permain...