
استلهمت من كتابة هذا المقال بمقال حول تحليل Sishny printf . ومع ذلك ، فقد ضاعت لحظة حول الطريقة التي تتبعها البيانات بعد أن تدخل الجهاز الطرفي. في هذه المقالة ، أريد تصحيح هذا العيب وتحليل مسار البيانات في الجهاز. سنكتشف أيضًا كيف تختلف المحطة الطرفية عن شركة شل ، وما هي الزاوية المزيفة ، وكيف تعمل أجهزة محاكاة الأطراف الطرفية ، وأكثر من ذلك بكثير.
الأساسيات
دعونا أولاً نفهم ماهية المحطة الطرفية ، و Shell ، و Console ، وكيف يختلف المحاكي الطرفي عن المحطة الطرفية العادية ، ولماذا سميت بهذا الاسم. لقد تم بالفعل كتابة الكثير من المعلومات حول هذا الأمر ، لذلك لن تسمع أي شيء جديد هنا. تقريبا جميع المعلومات هنا مأخوذة من الإنترنت ، وسأقدم روابط في نهاية المقال. من يعرف بالفعل ماذا تعني كل هذه الأشياء ، يمكنه تخطي هذا القسم بأمان.
محطة
الجهاز الطرفي عبارة عن مزيج من شاشة ولوحة مفاتيح ، أي جهاز فعلي. قبل أن تصبح الأجهزة الطرفية هذه المجموعة الخاصة ، كانت نوعًا من الأجهزة تسمى teleprinter (teletype ، teletypewriter أو TTY لفترة قصيرة) ، أي مجموعة من الطابعة ولوحة المفاتيح. عادةً ما يتم توصيل محطات متعددة إلى نفس الكمبيوتر. وبالتالي ، كان من الممكن العمل لعدة مستخدمين على نفس الكمبيوتر ، وكان لكل منهم جلسة خاصة به ، مستقلة عن الآخرين. تمت تسمية المحطة الطرفية لأنها كانت موجودة في نهاية كبل المحطة الطرفية.
هذا هو Teletype :

وهذا هو المحطة :

وحدة التحكم
وحدة التحكم (وحدة التحكم) - محطة متصلة مباشرة بالكمبيوتر. والحقيقة هي أن معظم المحطات الطرفية تم توصيلها ضمنيًا ، ولكن تم توصيل واحد على الأقل مباشرة بالكمبيوتر. تم السماح لوحدة التحكم باستخدام دائرة محددة بدقة من الأشخاص ، لأنها سمحت لك بتكوين الكمبيوتر.
قذيفة
إذا كان الجهازان السابقان عبارة عن أجهزة فعلية ، فإن هذا التعريف يشير حصريًا إلى البرنامج.
شل هي مترجم سطر الأوامر. الغرض الرئيسي هو تشغيل برامج أخرى. هناك عدد كبير من الأصداف المختلفة. الأكثر شيوعًا هو Bash (من English Bourne Again Shell ، والتي ، كما تقترح ويكيبيديا ، هي لعبة تميمة لشركة شل "Born again" ، أي شركة شل "التي تم إحياؤها"). أمثلة أخرى: اندفاعة (قذيفة خفيفة الوزن ، متوفرة إذا قمت بتشغيل ثنائي في / bin / sh) ، Zsh.
بالطبع ، لم تستطع كل من المحطات الطرفية ووحدات التحكم العثور على انعكاساتها في العصر الحديث. لذلك ، سوف نأخذ في الاعتبار أشياء مثل Emulator الطرفية و Virtual Console .
المحاكي الطرفي
المحاكي الطرفية - محاكي للمحطة القديمة الجيدة. مطلوب المحاكي الطرفي للبرامج التي لا يمكن أن تتفاعل مباشرة مع نظام X Window - Bash و Vim وغيرها.
دعونا أولا تحديد مسؤوليات المحطة:
- نقل إدخال المستخدم إلى جهاز كمبيوتر
- تسليم إخراج الكمبيوتر إلى الشاشة
لذا فإن جهاز المحاكاة الطرفية الخاص بنا يقوم بنفس الشيء تمامًا: فهو يقدم مدخلات المستخدم إلى البرنامج قيد التشغيل ، كما يعرض إخراج البرنامج على الشاشة. في أي حال ، يبقى المعنى - بين المستخدم والبرنامج قيد التشغيل ، هناك نوع من الطبقة المسؤولة عن الإدخال / الإخراج. أمثلة على المحاكي الطرفي: gnome-terminal ، xterm ، konsole.
من فضلك لا تخلط بين شل ومحطة الطرفية!
المحاكي الطرفي هو تطبيق واجهة المستخدم الرسومية ، أي نافذة في نظام X Window System. Shell عبارة عن مترجم لسطر الأوامر ، أي مجرد منفّذ أوامر ، وليس له غلاف رسومي. عند التحدث بشكل صحيح ، لا تقوم بتشغيل Bash ، بل تقوم بتشغيل Terminal Emulator ، الذي يقوم بتشغيل Bash داخل نفسه . المحاكي الطرفي وباش هما برنامجان مختلفان تمامًا. الأول هو المسؤول الوحيد عن الإدخال / الإخراج ، والثاني - عن أوامر المعالجة.
علاوة على ذلك ، ستشير جميع الإشارات إلى الجهاز إلى المحاكي الطرفي.
وحدة التحكم الافتراضية (المحطة الافتراضية)
اضغط على Ctrl + Alt + FN ، حيث تحتوي N عادة على قيم من 1 إلى 6. ما رأيته للتو يسمى Virtual Console (وحدة افتراضية) أو Virtual Terminal (Virtual terminal). تذكر ما قلته سابقًا عن المحطات؟ تم توصيل العديد من المحطات الطرفية بجهاز كمبيوتر واحد وكان كل محطة جلسة منفصلة ، مستقلة عن الآخرين. تكرر Virtual Console هذه الفكرة: قد تكون هناك عدة جلسات مستقلة داخل جهاز الكمبيوتر الخاص بك (ومع ذلك ، فمن الواضح أن موارد الكمبيوتر لا تزال مشتركة).
يمكنك تسمية هذا الكيان كلاً من Virtual Console و Virtual Terminal ، لأنه بحكم التعريف ، فإن وحدة التحكم هي محطة متصلة مباشرة بجهاز كمبيوتر ، ولكن جميع المحطات الافتراضية متصلة ، بشكل ما ، مباشرة بجهاز الكمبيوتر.
أجهزة TTY
يتم تعيين كل محطة جهاز TTY الخاص به (الجهاز الطرفي) ، والذي يوفر وحدة التحكم. على الرغم من أن teletypes من غير المرجح أن تجده ، إلا أن الانخفاض في TTY قد بقي حتى يومنا هذا.
يتكون جهاز TTY من عنصرين أساسيين:
- برنامج تشغيل الجهاز وهو مسؤول عن تقديم مدخلات لوحة المفاتيح للبرنامج وعرض إخراج البرنامج على الشاشة.
- الانضباط الخط TTY ( الانضباط الروسي الخط). الخط الانضباط هو واجهة وصول السائق ، والتي ، مع ذلك ، تجلب الكثير من المنطق لجهاز TTY. يمكننا أن نقول أن وكلاء الخط الانضباط يدعو للسائق. ما هو مجال مسؤولية هذا المكون ، وسوف نكتشف ذلك خلال المقال.
بناء جهاز TTY:

هناك 3 أنواع من أجهزة TTY:
- جهاز التحكم - يوفر تشغيل وحدة التحكم الافتراضية. يتم التحكم في إدخال وإخراج هذا الجهاز بالكامل بواسطة النواة.
- جهاز PTY (الطرف الزائف) - توفير التشغيل الطرفي في واجهة النافذة. يتم التحكم في مدخلات ومخرجات هذا الجهاز بواسطة محاكي طرفية يعمل في مساحة المستخدم.
- جهاز تسلسلي - يتصل مباشرة مع الأجهزة. لا يستخدم عادةً بشكل مباشر ، ولكنه موجود كأدنى مستوى في تنظيم بنية الجهاز الطرفي.
في هذه المقالة ، سنتحدث على وجه التحديد عن النوع الثاني من أجهزة TTY - المحطات الزائفة.
TTY خط الانضباط
نبدأ في فحص الانضباط لخط الجهاز TTY.
الميزة الأولى المهمة لخط الانضباط هي أنها مسؤولة عن معالجة I / O. يتضمن ذلك ، على سبيل المثال ، معالجة أحرف التحكم (انظر أحرف التحكم ) وتنسيق الإخراج. على سبيل المثال ، تقوم بإدخال أي نص ، لكنك تدرك فجأة أنك كنت مخطئًا في كتابة شيء ما وتريد مسحه - هذا هو المكان الذي يلعب فيه الانضباط المباشر.
سنحلل بالتفصيل ما يحدث بالضبط عندما نعمل في Bash يعمل في المحطة الطرفية. بشكل افتراضي ، يعمل جهاز TTY في الوضع الأساسي مع تمكين الصدى . الصدى هو عرض للأحرف التي أدخلتها على الشاشة.
عندما ندخل ، على سبيل المثال ، يتم إرسال الحرف a
، يتم إرسال هذا الحرف إلى جهاز TTY ، ولكن يتم اعتراضه من خلال نظام سطر TTY الخاص بالجهاز. إنها تقرأ حرفًا في المخزن المؤقت الداخلي لها ، وترى أن وضع echo
وضع التشغيل ، وتعرض الشخصية على الشاشة. في هذا الوقت ، لا يوجد شيء متاح للقراءة في البرنامج المتصل به الجهاز الطرفي. دعنا نضغط backspace
على لوحة المفاتيح. الرمز ^?
مرة أخرى تم اعتراضها بواسطة الانضباط الخطي ، والأخير ، يدرك أن المستخدم يريد محو آخر حرف تم إدخاله ، ويزيل هذه الشخصية من المخزن المؤقت الداخلي له ويمحو هذه الشخصية أيضًا من الشاشة. الآن ، إذا ضغطنا على Enter ، يقوم خط TTY Line Discipline أخيرًا بإرسال كل ما تمت كتابته مسبقًا إلى المخزن المؤقت للقراءة في الجهاز الطرفي ، بما في ذلك LF. في الوقت نفسه ، يتم عرض الأحرف CR و LF على الشاشة من أجل تحريك المؤشر إلى سطر جديد - هذا هو تنسيق الإخراج.
هذه هي الطريقة التي يعمل بها الوضع المتعارف عليه - حيث يقوم بنقل جميع الأحرف التي تم إدخالها إلى الجهاز فقط بعد الضغط على Enter
، ومعالجة أحرف التحكم وتنسيق الإخراج.
تحرير خط TTY
تحرير خط TTY هو المكون المسؤول عن معالجة المدخلات في مجال الانضباط. يجب القول أن تحرير الخط مفهوم عام ويتعلق بمعالجة المدخلات. على سبيل المثال ، لدى Bash و Vim تحرير خط خاص بهما.
يمكننا التحكم في إعدادات الانضباط في خط جهاز TTY الحالي باستخدام البرنامج stty . دعنا نجرب قليلا.
افتح Bash أو أي قذيفة أخرى واكتب:
stty icanon -echo
حاول الآن كتابة شيء ما ولن ترى مدخلاتك (لا تقلق ، لا يزال بإمكانك تمرير المدخلات إلى البرنامج). لقد قمت فقط بتعطيل الصدى - أي عرض الأحرف التي تم إدخالها على الشاشة. أدخل الآن:
stty raw echo
حاول كتابة شيء ما. ترى كيف يتم كسر الاستنتاج. ولكن لمزيد من التأثير ، دعنا نذهب إلى Dash - type /bin/sh
. حاول الآن إدخال أحرف خاصة ( Ctrl
+ أي حرف على لوحة المفاتيح) أو فقط اضغط على Enter
. أنت في حيرة - ما هي هذه الشخصيات الغريبة التي تظهر على الشاشة؟ والحقيقة هي أنه ، بعد دخولنا إلى أبسط Shell ، بالإضافة إلى Line Editing من الانضباط نفسه ، قمنا أيضًا بتعطيل Line Editing Bash ، والآن يمكننا أن نلاحظ مع القوة وأهم تأثير إدراج النمط الخام للانضباط للخط. هذا الوضع لا يعالج المدخلات على الإطلاق ولا ينسق الإخراج. لماذا هناك حاجة لوضع الخام؟ على سبيل المثال ، بالنسبة لـ Vim : يتم فتحه في إطار المحطة الطرفية بالكامل ويقوم بمعالجة الإدخال نفسه ، على الأقل بحيث لا تتقاطع الرموز الخاصة لانضباط الخط مع الرموز الخاصة لـ Vim نفسها.
لمزيد من الفهم ، دعونا نلقي نظرة على تخصيص أحرف التحكم. stty <control-character> <string>
الأمر.
أدخل في باش:
stty erase 0
الآن سيتم تعيين حرف التحكم في erase
إلى الحرف 0
. زر backspace
عادة ما يهم ^?
ولكن الآن سيتم إرسال هذه الشخصية الخاصة إلى PTS قراءة المخزن المؤقت حرفيًا - جربه بنفسك. يمكنك الآن محو الأحرف باستخدام زر 0
على لوحة المفاتيح ، لأنك طلبت من الانضباط tty line أن تتعرف على الحرف الذي تم إدخاله كحرف تحكم في erase
. يمكنك إرجاع الإعداد باستخدام الأمر stty erase ^\?
أو مجرد إغلاق المحطة ، لأننا أثرنا فقط على الجهاز tty الحالي.
يمكنك العثور على مزيد من المعلومات في الرجل stty .
المحاكي الطرفي و Pseudoterminal
في كل مرة نفتح فيها محطة طرفية جديدة في نظام X Window ، يفرز GNOME Terminal Server عملية جديدة ويطلق البرنامج الافتراضي فيها. عادة ، هذا نوع من أنواع Shell (على سبيل المثال ، Bash).
يحدث التواصل مع برنامج التشغيل من خلال ما يسمى Pseudoterminal ( pseudoterminal ( pseudoterminal ، PTY). توجد المحطة الزائفة نفسها في النواة ، ومع ذلك ، فإنها تتلقى مدخلات من مساحة المستخدم - من محاكي الجهاز الطرفي.
تتكون المحطة الزائفة من الجهازين الظاهريين التاليين:
1) PTY الرئيسي (PTM) - الجزء الرئيسي من محطة الزائفة. يستخدمه GNOME Terminal Server لنقل إدخال لوحة المفاتيح إلى برنامج يتم تشغيله داخل الجهاز ، وكذلك قراءة إخراج البرنامج وعرض الإخراج. يتصل خادم جنوم الطرفي بدوره بنظام X Window System عبر بروتوكول X.
2) PTY العبد (PTS) - جزء العبد من محطة الزائفة. يستخدمه برنامج يعمل داخل الجهاز لقراءة مدخلات لوحة المفاتيح وإخراج الشاشة على الشاشة. على الأقل ، البرنامج نفسه يعتقد ذلك (سأشرح ما يعنيه هذا ، أبعد من ذلك بقليل).
أي بيانات مسجلة في جهاز PTS هي مدخلات جهاز PTM ، أي ، تصبح قابلة للقراءة على جهاز PTM. والعكس صحيح: أي بيانات مسجلة في جهاز PTM هي مدخلات جهاز PTS. هذه هي الطريقة التي يتواصل بها GNOME Terminal Server والبرنامج الذي يعمل داخل الجهاز. يرتبط كل جهاز PTM بجهاز PTS الخاص به.
تبدو عملية إطلاق محطة جديدة مثل هذا:
1) ينشئ GNOME Terminal Server أجهزة رئيسية وعبودية عن طريق استدعاء وظيفة open () على جهاز خاص / dev / ptmx . إرجاع المكالمة open () واصف الملف لجهاز PTM الذي تم إنشاؤه - master_fd .
2) ينشئ GNOME Terminal Server عملية جديدة عن طريق استدعاء fork()
. هذه العملية ستكون المحطة الجديدة.
3) في محطة PTS ، يفتح الجهاز على واصفات الملفات 0 ، 1 ، 2 (stdin ، stdout و stderr ، على التوالي). الآن ، يتدفق الإدخال / الإخراج الطرفي القياسي إلى هذا الجهاز.
4) يتم إطلاق البرنامج المطلوب في المحطة الطرفية عن طريق استدعاء وظيفة exec()
. تبدأ بعض أعمال شل عادةً (على سبيل المثال ، باش). أي برنامج يتم تشغيله لاحقًا من Bash سيكون له نفس واصفات الملف مثل Bash ، أي سيتم توجيه تدفقات البرنامج إلى جهاز PTS.
يمكنك أن ترى بنفسك أين يتم توجيه تدفقات خرج المحطة الطرفية القياسية باستخدام الأمر ls -la /proc/self/fd
:

يقع جهاز PTS على المسار / dev / pts / N ، والمسار إلى جهاز PTM لا يهمنا على الإطلاق. الحقيقة هي أن GNOME Terminal Server يحتوي بالفعل على واصف ملف لجهاز PTM المفتوح ولا يحتاج إلى مسار إليه ، ومع ذلك ، في العملية الفرعية ، يجب أن نفتح جهاز PTS على تدفقات الإخراج القياسية عن طريق استدعاء وظيفة open()
، والتي تتطلب مسار الملف.
تذكر ، قلت أن البرنامج الذي يستخدم جهاز PTS يعتقد فقط أنه يتصل مباشرة مع الجهاز؟ الحقيقة هي أن PTS هو أيضًا جهاز طرفي ( جهاز TTY) ، ولكن الفرق بين جهاز PTS وجهاز TTY الفعلي هو أن جهاز PTS يتلقى الإدخال ليس من لوحة المفاتيح ، ولكن من الجهاز الرئيسي ، والإخراج لا ينتقل إلى الشاشة ، ولكن سيد الجهاز. هذا هو السبب في أن محطة الزائفة سميت بهذا الشكل - المحطة الزائفة تشبه فقط (مرة أخرى؟) المحطة. الفرق بين المحاكي الطرفي والمحطة الزائفة هو أن المحاكي الطرفي هو مجرد برنامج رسومي يتيح لك تشغيل الجهاز مباشرة داخل واجهة النافذة ، ولكن يتم تنفيذ هذه الميزة باستخدام الطرف الزائف.
حقيقة أن الجهاز PTS هو جهاز TTY مهم جدا. إليك السبب:
- يحتوي البرنامج الذي يتصل به الجهاز الطرفي على كافة إمكانيات الجهاز الطرفي التقليدي. على سبيل المثال: تعطيل الصدى ، تعطيل / تمكين العرض الكنسي.
- البرنامج ، مع العلم أن الجهاز الطرفي متصل به (يقال أن البرنامج يحتوي على محطة تحكم) ، يمكن أن يعمل بشكل تفاعلي ويطلب من المستخدم إدخاله. على سبيل المثال ، اطلب اسم مستخدم وكلمة مرور.
- يوجد أيضًا نظام TTY Line Discipline ، لذلك لدينا القدرة على معالجة أحرف التحكم قبل أن تصل إلى البرنامج ، بالإضافة إلى تنسيق إخراج البرنامج.
جهاز PTM هو أيضًا جهاز TTY ، لكن هذا لا يلعب أي دور ، لأنه لا يستخدم كجهاز تحكم. علاوة على ذلك ، يتم ضبط الانضباط الخطي لجهاز PTM على الوضع الأولي ، وبالتالي ، لا تتم المعالجة عند نقل البيانات من PTS إلى جهاز PTM. ومع ذلك ، لا يزال يتم تقديم المكالمات إلى read()
write()
من مساحة المستخدم أولاً عن طريق الانضباط على الجهازين. ستلعب هذه اللحظة دورًا أكبر ، كما سنرى لاحقًا.
فيما يلي عملية الاتصال بين خادم جنوم الطرفي والبرنامج الذي يعمل داخل الجهاز:

يجدر بنا أن نفحص بمزيد من التفصيل الدور الذي يلعبه الانضباط الخطي في التواصل بين كلا الجزأين من محطة زائفة. هنا ، يكون الخط الانضباط مسؤولاً عن معالجة البيانات التي تمر من PTM إلى جهاز PTS ، وكذلك عن تسليم البيانات من جزء من محطة الزائفة إلى آخر. عندما نكون في برنامج تشغيل الجهاز PTS ، فإننا نشارك الانضباط الخط لجهاز PTM ، والعكس بالعكس.
الأجهزة الافتراضية
ربما كنت تعتقد أنه يمكنك فتح الملف على طول المسار / dev / pts / N وكتابة أو قراءة البيانات منه ، من ملف نصي عادي؟ نعم ، كل الأجهزة الموجودة على الأنظمة المشابهة لنظام Unix هي ملفات ، وذلك بفضل المبدأ الأساسي لنظام Unix ، والذي ينص على أن كل شيء ملف. ومع ذلك ، لا توجد ملفات خاصة بالأجهزة (الإنجليزية - ملف الجهاز) هي ملفات نصية. تسمى هذه الأجهزة الأجهزة الافتراضية - أي أنها موجودة حصريًا في الذاكرة وليس على القرص.
لا تحاول فتح هذه الملفات كملفات نصية عادية. ومع ذلك ، يمكنك استخدام هذه الأجهزة من خلال عمليات write()
و read()
، والتي سيتم تشغيل الاتصال بها بواسطة برنامج تشغيل الجهاز. دعنا نحاول القيام بذلك.
افتح نافذتين طرفيتين وأدخل tty
في كل أمر. سيُظهر هذا الأمر أي جهاز TTY يخدم الجهاز النشط حاليًا. الآن أدخل echo "Hello, World!" > /dev/pts/N
echo "Hello, World!" > /dev/pts/N
في النافذة الطرفية الأولى ، حيث N هو مؤشر PTS لجهاز النافذة الثانية ، قم بالتبديل إلى النافذة الثانية وسترى المدخلات الخاصة بك من النافذة الأولى. لقد قمت الآن بكتابة البيانات إلى جهاز PTS الخاص بالإطار الثاني كما لو كانت قد تم إجراؤها بواسطة برنامج يعمل في تلك المحطة .

الجهاز الطرفي الزائف
إننا نقترب أكثر فأكثر من الجزء الأخير من المقالة ، لكن قبل ذلك نلقي نظرة "تحت غطاء" نظام Linux - فكر في جهاز الطرف الزائف على مستوى النواة. سيكون هناك الكثير من التعليمات البرمجية ، لكنني سأحاول شرح كل مجموعة من التعليمات البرمجية المعينة بأكبر قدر ممكن من التفاصيل ، وتقليل التفاصيل غير المهمة والانتقال بالتسلسل.
قبل البدء ، نقدم ما يسمى "سلة المكونات". بينما نتحرك على طول القلب ، سنضيف المزيد والمزيد من المكونات إليه ونجد صلة بينهما. آمل أن يكون هذا يساعدك على فهم أفضل للجهاز الزائف. لنبدأ.
عند بدء تشغيل Linux ، يقوم بتحميل برامج تشغيل الأجهزة الضرورية. لدينا محطة الزائفة لديها أيضا مثل هذا السائق. يبدأ تسجيلها بدعوة لهذه الوظيفة:
static int __init pty_init(void) { legacy_pty_init(); unix98_pty_init();
بالنسبة إلى جميع الأنظمة الحديثة ، سيتم استدعاء الدالة unix98_pty_init()
:
static void __init unix98_pty_init(void) { ptm_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX, TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM | TTY_DRIVER_DYNAMIC_ALLOC); if (IS_ERR(ptm_driver)) panic("Couldn't allocate Unix98 ptm driver"); pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX, TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_DEVPTS_MEM | TTY_DRIVER_DYNAMIC_ALLOC); if (IS_ERR(pts_driver)) panic("Couldn't allocate Unix98 pts driver"); ptm_driver->driver_name = "pty_master"; ptm_driver->name = "ptm"; ptm_driver->major = UNIX98_PTY_MASTER_MAJOR; ptm_driver->minor_start = 0; ptm_driver->type = TTY_DRIVER_TYPE_PTY; ptm_driver->subtype = PTY_TYPE_MASTER; ptm_driver->init_termios = tty_std_termios; ptm_driver->init_termios.c_iflag = 0; ptm_driver->init_termios.c_oflag = 0; ptm_driver->init_termios.c_cflag = B38400 | CS8 | CREAD; ptm_driver->init_termios.c_lflag = 0; ptm_driver->init_termios.c_ispeed = 38400; ptm_driver->init_termios.c_ospeed = 38400; ptm_driver->other = pts_driver; tty_set_operations(ptm_driver, &ptm_unix98_ops); pts_driver->driver_name = "pty_slave"; pts_driver->name = "pts"; pts_driver->major = UNIX98_PTY_SLAVE_MAJOR; pts_driver->minor_start = 0; pts_driver->type = TTY_DRIVER_TYPE_PTY; pts_driver->subtype = PTY_TYPE_SLAVE; pts_driver->init_termios = tty_std_termios; pts_driver->init_termios.c_cflag = B38400 | CS8 | CREAD; pts_driver->init_termios.c_ispeed = 38400; pts_driver->init_termios.c_ospeed = 38400; pts_driver->other = ptm_driver; tty_set_operations(pts_driver, &pty_unix98_ops); if (tty_register_driver(ptm_driver)) panic("Couldn't register Unix98 ptm driver"); if (tty_register_driver(pts_driver)) panic("Couldn't register Unix98 pts driver"); tty_default_fops(&ptmx_fops); ptmx_fops.open = ptmx_open; cdev_init(&ptmx_cdev, &ptmx_fops); if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) || register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0) panic("Couldn't register /dev/ptmx driver"); device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
نحن مهتمون هنا بثلاثة أشياء:
- يستدعي
tty_set_operatons
لبرنامج تشغيل pty master وأجهزة الرقيق pty. - وظيفة
ptmx_open
، المسؤولة عن إنشاء كلا الجزأين من الجهاز الطرفي المزيف عند فتح الجهاز / dev / ptmx الخاص . هام: / dev / ptmx ليس جهاز PTM ، ولكنه مجرد واجهة لإنشاء محطة زائفة جديدة. - تسجيل برامج تشغيل الجهاز PTM و PTS.
دعنا نذهب بالترتيب:
1. tty_set_operations
تقوم الدالة tty_set_operations () فقط بتعيين جدول دالة لبرنامج التشغيل الحالي:
void tty_set_operations(struct tty_driver *driver, const struct tty_operations *op) { driver->ops = op; };
هيكل tty_operations هو جدول الوظائف الذي يستخدم للوصول إلى وظائف برنامج تشغيل الجهاز TTY.
pty_unix98_ops
أهم شيء في الهياكل pty_unix98_ops
و ptm_unix98_ops
، والتي هي جدول وظائف الأجزاء المقابلة من المحطة الزائفة:
static const struct tty_operations ptm_unix98_ops = { .install = pty_unix98_install, .remove = pty_unix98_remove, .open = pty_open, .close = pty_close, .write = pty_write,
هنا يمكنك مراقبة وظيفة pty_write ، pty_write
مألوفة بالفعل من مقالة Sishny printf - سنعود إليها لاحقًا.
دعنا نضيف هذا الهيكل إلى سلة المكونات لدينا:

كما ترون ، الطرق الرئيسية لكلا السائقين ليست مختلفة على الإطلاق. بالمناسبة ، لاحظ أنه لا توجد وظيفة لعملية read () - لا يوجد شيء مثل pty_read()
. الحقيقة هي أن القراءة ستخدم فقط عن طريق الانضباط الخطي. وبالتالي ، فإننا نتعرف على الميزة الهامة الثانية لخط الانضباط - قراءة البيانات من جهاز TTY.
2. ptmx_open
الآن دعنا ننتقل إلى ptmx_open () :
static int ptmx_open(struct inode *inode, struct file *filp) { struct tty_struct *tty;
نحن مهتمون tty_init_dev()
، حيث الوسيطة الأولى هي برنامج تشغيل جهاز PTM ، والثاني هو فهرس الجهاز. هنا نترك منطقة مسؤولية برنامج تشغيل PTY وننتقل إلى الملف ، المسؤول فقط عن الأجهزة TTY العامة ولا يعرف أي شيء عن محطة الزائفة لدينا.
struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx) { struct tty_struct *tty; tty = alloc_tty_struct(driver, idx); retval = tty_driver_install_tty(driver, tty); retval = tty_ldisc_setup(tty, tty->link);
أولاً ، سنعمل على alloc_tty_struct()
:
struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx) { struct tty_struct *tty; tty = kzalloc(sizeof(*tty), GFP_KERNEL);
الشيء الوحيد الذي يثير اهتمامنا هنا هو وظيفة tty_ldisc_init()
:
int tty_ldisc_init(struct tty_struct *tty) { struct tty_ldisc *ld = tty_ldisc_get(tty, N_TTY); if (IS_ERR(ld)) return PTR_ERR(ld); tty->ldisc = ld;
الذي يدعو tty_ldisc_get()
:
static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc) { struct tty_ldisc *ld;
لذلك ، قمنا بفحص استدعاء الدالة alloc_tty_struct()
، التي تنشئ بنية tty_struct جنبًا إلى جنب مع خط الانضباط - بنية tty_ldisc . كل من الهياكل لها روابط لبعضها البعض. دعونا نلقي نظرة فاحصة على هذه الهياكل.
- tty_struct عبارة عن بنية للوصول إلى برنامج تشغيل جهاز TTY وبعض الحقول الأخرى. يبدو مثل هذا:
struct tty_struct { struct tty_driver *driver;
- tty_ldisc هو هيكل الانضباط لخط TTY للجهاز. يتكون من حقلين فقط ويبدو كما يلي:
struct tty_ldisc { struct tty_ldisc_ops *ops;
يبدو أن لا شيء معقد؟ لنقم بإضافة جميع الهياكل التي تم اعتبارها حتى هذه النقطة إلى سلتنا وربطها بالطريقة نفسها المرتبطة في الكود:

لكننا أنشأنا tty_struct فقط لجهاز PTM. ماذا عن الجهاز PTS؟ للقيام بذلك ، نعود إلى وظيفة tty_init_dev()
ونتذكر أنه من المتوقع أن ندعو بعد ذلك إلى وظيفة tty_driver_install_tty()
:
static int tty_driver_install_tty(struct tty_driver *driver, struct tty_struct *tty) { return driver->ops->install ? driver->ops->install(driver, tty) : tty_standard_install(driver, tty); }
يخبرنا التعليق أن هذه الطريقة مسؤولة عن إنشاء هياكل إضافية متنوعة. الجهاز PTS وسوف يكون لدينا هيكل إضافي. أعترف ، كان الأمر مفاجئًا للغاية بالنسبة لي ، لأنه ، الجحيم ، الجهاز بالكامل ، وليس مجرد نوع من البنية الإضافية! لكننا ندرك جميعًا أن جميع الأجهزة ليست سوى نوع من البنية ، لذلك تابع. حسنًا ، ما هو driver-> ops-> التثبيت هنا ؟ للقيام بذلك ، انظر جدول الوظائف لبرنامج تشغيل PTM مرة أخرى:
static const struct tty_operations ptm_unix98_ops = { .install = pty_unix98_install,
ونفهم أننا مهتمون pty_unix98_install()
:
static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty) { return pty_common_install(driver, tty, false); }
الذي يستدعي وظيفة pty_common_install()
:
static int pty_common_install(struct tty_driver *driver, struct tty_struct *tty, bool legacy) { struct tty_struct *o_tty;
, PTS tty_struct , PTS . . tty_struct PTS .
, TTY ( - ?).
— , PTM, PTS :
static const struct file_operations tty_fops = { .llseek = no_llseek, .read = tty_read, .write = tty_write, .poll = tty_poll, .unlocked_ioctl = tty_ioctl, .compat_ioctl = tty_compat_ioctl, .open = tty_open, .release = tty_release, .fasync = tty_fasync, .show_fdinfo = tty_show_fdinfo, };
, TTY .
. , /dev/ptmx . , PTS , , PTM , :

مرحبا العالم!
. "Hello, World!", .
#include <stdio.h> void main() { printf("Hello, World!\n"); }
, "Hello, World!" . , , , . , . stdout /dev/null — . , Linux.
Unix write() , read() , close() , write() /dev/pts/0 __vfs_write()
:
ssize_t __vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { ssize_t ret;
write() . , :
static const struct file_operations tty_fops = {
tty_write()
:
static ssize_t tty_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct tty_struct *tty = file_tty(file); struct tty_ldisc *ld; ssize_t ret; ld = tty_ldisc_ref_wait(tty); ret = do_tty_write(ld->ops->write, tty, file, buf, count); tty_ldisc_deref(ld); return ret; }
tty_struct TTY , write() . :
static struct tty_ldisc_ops n_tty_ops = { .write = n_tty_write,
n_tty_write()
:
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *buf, size_t nr) { const unsigned char *b = buf;
, "Hello, World!" write() PTS . :
static const struct tty_operations pty_unix98_ops = { .write = pty_write,
pty_write()
:
static int pty_write(struct tty_struct *tty, const unsigned char *buf, int c) { struct tty_struct *to = tty->link;
:
__vfs_write() ->
. , PTM . , .
, flip buffer . Flip buffer — , . tty driver , . , . , , . , , . - flip buffer — (, - , flip).
, . tty_insert_flip_string()
tty_insert_flip_string_fixed_flag()
, PTM :
int tty_insert_flip_string_fixed_flag(struct tty_port *port, const unsigned char *chars, char flag, size_t size) { int copied = 0; do { int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE);
, flip buffer , , . , — PTM , .
, "Hello, World!" PTM . GNOME Terminal Server poll() ( I/O) master . , ? لا يهم كيف. , , — .
tty_flip_buffer_push()
( pty_write):
void tty_flip_buffer_push(struct tty_port *port) { tty_schedule_flip(port); }
tty_schedule_flip()
, , :
void tty_schedule_flip(struct tty_port *port) { struct tty_bufhead *buf = &port->buf; smp_store_release(&buf->tail->commit, buf->tail->used); queue_work(system_unbound_wq, &buf->work); }
, work (, - ) , — , flush_to_ldisc()
:
static void flush_to_ldisc(struct work_struct *work) { struct tty_port *port = container_of(work, struct tty_port, buf.work);
receive_buf()
__receive_buf()
, :
static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) { struct n_tty_data *ldata = tty->disc_data; bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty)); if (ldata->real_raw) n_tty_receive_buf_real_raw(tty, cp, fp, count); else if (ldata->raw || (L_EXTPROC(tty) && !preops)) n_tty_receive_buf_raw(tty, cp, fp, count); else if (tty->closing && !L_EXTPROC(tty)) n_tty_receive_buf_closing(tty, cp, fp, count); else { if (ldata->lnext) { char flag = TTY_NORMAL; if (fp) flag = *fp++; n_tty_receive_char_lnext(tty, *cp++, flag); count--; } if (!preops && !I_PARMRK(tty)) n_tty_receive_buf_fast(tty, cp, fp, count); else n_tty_receive_buf_standard(tty, cp, fp, count); } if (read_cnt(ldata)) { kill_fasync(&tty->fasync, SIGIO, POLL_IN); wake_up_interruptible_poll(&tty->read_wait, EPOLLIN); } }
, n_tty_receive_buf ( , _raw) read_buf , TTY . PTM raw , read_buf. , PTM PTS , .
, :
... pty_write() ->
, PTM — PTS .
: PTM . GNOME Terminal Server "Hello, World!", read() PTM . read() write() — n_tty_read()
. , , — read_buf — . GNOME Terminal Server X Server, .
, "Hello, World!" :
-> PTY slave -> PTY master -> GNOME-TERMINAl-SERVER -> X Server -> ->
استنتاج
. :
- TTY
- ,
, ! - — , !
مصادر