Hello World هو أحد البرامج الأولى التي نكتبها بأي لغة برمجة.
بالنسبة لـ C ، يبدو عالم الترحيب بسيطًا وقصيرًا:
#include <stdio.h> void main() { printf("Hello World!\n"); } 
نظرًا لأن البرنامج قصير جدًا ، يجب أن يكون من الضروري شرح ما يحدث "تحت الغطاء".
أولاً ، دعونا نرى ما يحدث عند التجميع والربط:
gcc --save-temps hello.c -o hello--save-temps بحيث يترك gcc 
hello.s ، ملف رمز التجميع.
فيما يلي نموذج مجمع الشفرة الذي تلقيته:
  .file "hello.c" .section .rodata .LC0: .string "Hello World!" .text .globl main .type main, @function main: pushq %rbp movq %rsp, %rbp movl $.LC0, %edi call puts popq %rbp ret 
كما ترون من قائمة المجمّع ، فإنه ليس 
printf يسمى ، ولكن 
puts . يتم أيضًا تعريف وظيفة 
puts في ملف 
stdio.h وتلتزم بطباعة سطر وكسر فاصل.
حسنًا ، لقد فهمنا الوظيفة التي تستدعيها الكود. ولكن أين يتم 
puts تنفذ؟
لتحديد أي مكتبة تطبق ، نستخدم 
ldd ، الذي يعرض تبعيات المكتبة ، و 
nm ، والذي يعرض أحرف ملف الكائن.
 $ ldd hello libc.so.6 => /lib64/libc.so.6 (0x0000003e4da00000) $ nm /lib64/libc.so.6 | grep " puts" 0000003e4da6dd50 W puts 
توجد الوظيفة في مكتبة تسمى 
libc وتقع في 
/lib64/libc.so.6 على 
/lib64/libc.so.6 (Fedora 19). في حالتي ، يعد 
/lib64 على 
/usr/lib64 ، و 
/lib64 هو 
/lib64 على 
/usr/lib64 . هذا الملف يحتوي على جميع الوظائف.
اكتشفنا إصدار 
libc عن طريق تشغيل الملف كما لو كان قابلاً للتنفيذ:
 $ /usr/lib64/libc-2.17.so GNU C Library (GNU libc) stable release version 2.17, by Roland McGrath et al. ... 
نتيجة لذلك ، يستدعي برنامجنا وظيفة 
puts من 
glibc الإصدار 2.17. دعونا الآن نرى ما تقوم به وظيفة 
puts في 
glibc-2.17 .
يصعب التنقل برمز glibc بسبب الاستخدام الواسع النطاق لوحدات ما قبل المعالجة والبرامج النصية. بالنظر إلى الكود ، نرى ما يلي في 
libio/ioputs.c :
 weak_alias (_IO_puts, puts) 
في glibc ، هذا يعني أنه عند استدعاء المكالمات ، يتم استدعاء 
_IO_puts بالفعل. تم توضيح هذه الوظيفة في نفس الملف ، ويبدو الجزء الرئيسي من هذه الوظيفة كما يلي:
 int _IO_puts (str) const char *str; {  
رميت كل القمامة حول التحدي المهم لنا. الآن 
_IO_sputn هو 
_IO_sputn الحالي في سلسلة مكالمات hello world. نجد تعريفًا ، هذا الاسم هو ماكرو معرف في 
libio/libioP.h والذي يستدعي ماكرو آخر ، والذي مرة أخرى ... تحتوي شجرة الماكرو على الشخص التالي:
  #define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)  
ماذا بحق الجحيم يحدث هنا؟ دعنا نوسع كل وحدات الماكرو للنظر في الكود النهائي:
  ((*(struct _IO_jump_t **) ((void *) &((struct _IO_FILE_plus *) (((_IO_FILE*)(&_IO_2_1_stdout_)) ) )->vtable+(((_IO_FILE*)(&_IO_2_1_stdout_)) )->_vtable_offset))->__xsputn ) (((_IO_FILE*)(&_IO_2_1_stdout_)), str, len) 
عيون تؤذي. اسمحوا لي أن أشرح ما يحدث هنا. يستخدم Glibc جدول القفز لاستدعاء وظائف. في حالتنا ، يقع الجدول في بنية تسمى 
_IO_2_1_stdout_ ، وتسمى الوظيفة التي نحتاج إليها 
__xsputn .
تم التصريح عن البنية في ملف 
libio/libio.h :
 extern struct _IO_FILE_plus _IO_2_1_stdout_; 
وفي الملف 
libio/libioP.h تعريفات للهيكل والجدول 
libio/libioP.h :
 struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable; };  
إذا 
_IO_2_1_stdout_ أعمق ، 
_IO_2_1_stdout_ أن الجدول 
_IO_2_1_stdout_ تهيئته في الملف 
libio/stdfiles.c ، ويتم تحديد التطبيقات 
libio/stdfiles.c لوظائف الجدول في 
libio/fileops.c :
  DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);  # define _IO_new_file_xsputn _IO_file_xsputn  
كل هذا يعني أننا إذا استخدمنا جدول القفز المرتبط بـ 
stdout ، فسنقوم في النهاية باستدعاء الدالة 
_IO_new_file_xsputn . بالفعل أقرب ، أليس كذلك؟ هذه الوظيفة تلقي البيانات في المخازن المؤقتة وتدعو 
new_do_write عندما يمكن إخراج محتويات المخزن المؤقت. هذا ما يبدو عليه 
new_do_write :
 static _IO_size_t new_do_write (fp, data, to_do) _IO_FILE *fp; const char *data; _IO_size_t to_do; { _IO_size_t count; .. count = _IO_SYSWRITE (fp, data, to_do); .. return count; } 
بالطبع ، الماكرو يسمى. من خلال نفس آلية الانتقال 
__xsputn لـ 
__xsputn ، يتم 
__write . بالنسبة لملفات 
__write ، 
__write _IO_new_file_write إلى 
_IO_new_file_write . وتسمى هذه الوظيفة في نهاية المطاف. دعنا ننظر لها؟
 _IO_ssize_t _IO_new_file_write (f, data, n) _IO_FILE *f; const void *data; _IO_ssize_t n; { _IO_ssize_t to_do = n; _IO_ssize_t count = 0; while (to_do > 0) {  
أخيرًا ، وظيفة تستدعي شيئًا لا يبدأ بتسطير أسفل السطر! وظيفة 
write معروفة ومحددة في 
unistd.h . هذه طريقة قياسية إلى حد ما لكتابة بايت إلى ملف باستخدام واصف ملف. يتم تعريف وظيفة 
write في glibc نفسها ، لذلك نحن بحاجة إلى العثور على الكود.
لقد وجدت رمز 
write في 
sysdeps/unix/syscalls.list . يتم إنشاء معظم مكالمات النظام ملفوفة في glibc من هذه الملفات. يحتوي الملف على اسم الوظيفة والوسيطات التي تتطلبها. يتم إنشاء نص الدالة من نمط استدعاء نظام شائع.
 # File name Caller Syscall name Args Strong name Weak names ... write - write Ci:ibn __libc_write __write write ... 
عند استدعاء رمز glibc 
write (إما 
__libcwrite أو 
__write ) ، يحدث syscall في kernel. رمز Kernel أكثر قابلية للقراءة من glibc. نقطة الإدخال إلى syscall 
write في 
fs/readwrite.c :
 SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count) { struct fd f = fdget(fd); ssize_t ret = -EBADF; if (f.file) { loff_t pos = file_pos_read(f.file); ret = vfs_write(f.file, buf, count, &pos); if (ret >= 0) file_pos_write(f.file, pos); fdput(f); } return ret; } 
أولاً ، تم العثور على البنية المتوافقة مع واصف الملف ، ثم يتم 
vfs_write الدالة 
vfs_write من النظام الفرعي لنظام الملفات الظاهري (vfs). سيتوافق الهيكل في حالتنا مع ملف 
stdout . ألقِ نظرة على 
vfs_write :
 ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { ssize_t ret;  
تفوض الوظيفة تنفيذ وظيفة 
write الخاصة بملف معين. في نظام Linux ، غالبًا ما يتم تطبيق ذلك في رمز برنامج التشغيل ، لذلك تحتاج إلى معرفة برنامج التشغيل الذي يسمى في حالتنا.
أستخدم Fedora 19 مع Gnome 3. لإجراء التجارب ، ويعني هذا على وجه الخصوص أن الجهاز الطرفي هو 
gnome-terminal افتراضيًا. قم بتشغيل هذا الجهاز ونفذ ما يلي:
 ~$ tty /dev/pts/0 ~$ ls -l /proc/self/fd total 0 lrwx------ 1 kos kos 64 okt. 15 06:37 0 -> /dev/pts/0 lrwx------ 1 kos kos 64 okt. 15 06:37 1 -> /dev/pts/0 lrwx------ 1 kos kos 64 okt. 15 06:37 2 -> /dev/pts/0 ~$ ls -la /dev/pts total 0 drwxr-xr-x 2 root root 0 okt. 10 10:14 . drwxr-xr-x 21 root root 3580 okt. 15 06:21 .. crw--w---- 1 kos tty 136, 0 okt. 15 06:43 0 c--------- 1 root root 5, 2 okt. 10 10:14 ptmx 
يقوم الأمر 
tty بطباعة اسم الملف المرتبط بالإدخال القياسي ، وكما ترون من قائمة الملفات في 
/proc ، يرتبط الملف نفسه 
/proc ودفق الأخطاء. تسمى ملفات الجهاز الموجودة في 
/dev/pts زائفة ، وبصورة أكثر دقة ، فهي محطات زائفة للرقيق. عندما تكتب عملية ما إلى محطة زائفة للرقيق ، تنتقل البيانات إلى الوحدة الزائفة الرئيسية. ماجستير محطة الزائفة هو جهاز 
/dev/ptmx .
يوجد برنامج تشغيل الجهاز الزائف في Linux kernel في 
drivers/tty/pty.c :
 static void __init unix98_pty_init(void) {  
عند الكتابة إلى 
pts ، يتم 
pty_write ، والذي يبدو كما يلي:
 static int pty_write(struct tty_struct *tty, const unsigned char *buf, int c) { struct tty_struct *to = tty->link; if (tty->stopped) return 0; if (c > 0) {  c = tty_insert_flip_string(to->port, buf, c);  if (c) { tty_flip_buffer_push(to->port); tty_wakeup(tty); } } return c; } 
تساعد التعليقات في فهم أن البيانات في قائمة انتظار الإدخال لجهاز الكمبيوتر الطرفي الرئيسي المزيف. لكن من يقرأ من هذا الخط؟
 ~$ lsof | grep ptmx gnome-ter 13177 kos 11u CHR 5,2 0t0 1133 /dev/ptmx gdbus 13177 13178 kos 11u CHR 5,2 0t0 1133 /dev/ptmx dconf 13177 13179 kos 11u CHR 5,2 0t0 1133 /dev/ptmx gmain 13177 13182 kos 11u CHR 5,2 0t0 1133 /dev/ptmx ~$ ps 13177 PID TTY STAT TIME COMMAND 13177 ? Sl 0:04 /usr/libexec/gnome-terminal-server 
gnome-terminal-server عملية 
gnome-terminal-server all جميع 
gnome-terminal وتخلق محطات زائفة جديدة. هو الذي يستمع إلى محطة الزائفة الرئيسية ، وفي النهاية ، سيتلقى بياناتنا ، وهي 
"Hello World" . يستقبل خادم 
gnome-terminal السلسلة ويعرضها على الشاشة. بشكل عام ، لم يكن هناك ما يكفي من الوقت لتحليل مفصل 
gnome-terminal :)
الخاتمة
المسار العام لخط "Hello World" الخاص بنا:
 0. hello: printf("Hello World") 1. glibc: puts() 2. glibc: _IO_puts() 3. glibc: _IO_new_file_xsputn() 4. glibc: new_do_write() 5. glibc: _IO_new_file_write() 6. glibc: syscall write 7. kernel: vfs_write() 8. kernel: pty_write() 9. gnome_terminal: read() 10. gnome_terminal: show to user 
يبدو وكأنه تمثال نصفي 
قليلا لهذه العملية البسيطة. من الجيد أن يراها فقط أولئك الذين يريدونها حقًا.