WriteFile أرجع نجاحاً. حسناً، أين البيانات الآن؟
الجواب، شبه المؤكَّد: ما زالت ليست على القرص. نُسخت فحسب إلى ذاكرة مؤقّتة في الذاكرة. لذلك يحدث «حفظتُ، وبعد انقطاع الطاقة اختفت»؛ لذلك تُظهر معايير نسخ الملفّات سرعات مستحيلة فيزيائيّاً؛ ولذلك تستدعي قواعد البيانات fsync بأمانة.
الجزء 4 من سلسلة «أعماق إدخال/إخراج Windows» عن الشيء الذي يقف في الوسط: مدير الذاكرة المؤقّتة. في الجزء 2 كتبنا «إن كانت البيانات في الذاكرة المؤقّتة أصلاً، يكتمل حتّى الإدخال/الإخراج اللاتزامنيّ تزامنيّاً»، وفي الجزء 1 تركنا «الطريق المختصر الذي لا يبني IRP — الإدخال/الإخراج السريع» واجباً منزليّاً. هذه الحلقة تسترجع الخيطين كليهما.
1. الخلاصة أوّلاً
- ذاكرة ملفّات Windows المؤقّتة من نوع الكتابة اللاحقة (write-back). القراءة أوّلاً من ذاكرة الملفّات المؤقّتة في النظام، والكتابة أوّلاً إليها أيضاً. الانعكاس على القرص يتولاّه نظام التشغيل لاحقاً.1
- كيان الذاكرة المؤقّتة تعيين ملفّ. يعيّن مدير الذاكرة المؤقّتة مقاطع الملفّ بوحدات 256KB في فضاء عناوين النظام، وتصير القراءة والكتابة «نسخ ذاكرة بين ذلك العرض ومخزن التطبيق المؤقّت» (الفصل 2).1
- الكتابة يلحقها lazy writer المؤجَّل كلّ ثانية. انهيار التطبيق لا يفقد البيانات، لكن انقطاع الطاقة أو انهيار نظام التشغيل يفقد الذاكرة المؤقّتة المتّسخة (الفصل 4).1
- أدوات «الكتابة يقيناً» ثلاثة.
FlushFileBuffers(يعادلهFlush(true)في .NET)، وFILE_FLAG_WRITE_THROUGH، وFILE_FLAG_NO_BUFFERING. التفريغ في كلّ كتابة متكرّرة غير كفء، والوثائق الرسميّة تذكر الجمع بين NO_BUFFERING وWRITE_THROUGH (الفصل 5).21 - لـ NO_BUFFERING شروط محاذاة. الحجم والإزاحة مضاعف صحيح لحجم القطاع، وعنوان المخزن المؤقّت محاذٍ على حدود القطاع المادّيّ. وحتّى مع NO_BUFFERING تظلّ البيانات الوصفيّة تُخزَّن مؤقّتاً (القسم 5.3).31
- عرض التعيين والذاكرة المؤقّتة يشتركان في البيانات نفسها. الملفّ المعيَّن في الذاكرة والإدخال/الإخراج المخزَّن مؤقّتاً العاديّ متّسقان، وإدامة التعيين درجتان:
FlushViewOfFileثمّFlushFileBuffers(الفصل 6).45 - القراءة والكتابة التزامنيّتان اللتان تصيبان الذاكرة المؤقّتة قد لا تصنعان IRP أصلاً. طريق الإدخال/الإخراج السريع المختصر يذهب مباشرة إلى مدير الذاكرة المؤقّتة — جواب واجب الجزء 1 (الفصل 7).6
خريطة المعرفة لهذه المقالة
نجاح WriteFile لا يعني الديمومة على القرص: تنسخ ذاكرة ملفّات Windows أوّلاً إلى عرض ذاكرة النظام المؤقّتة، ثمّ يلحق lazy writer الانعكاس على القرص كلّ ثانية. عند انقطاع الكهرباء أو انهيار نظام التشغيل تُفقَد الصفحات المتّسخة التي لم تُكتَب بعد، لذا تتطلّب البيانات التي يُراد كتابتها يقيناً الاختيار بين FlushFileBuffers وFILE_FLAG_WRITE_THROUGH وFILE_FLAG_NO_BUFFERING.
flowchart LR
accTitle: خريطة معرفة مدير الذاكرة المؤقّتة والكتابة المتأخّرة
accDescr: مخطّط يبيّن علاقات مدير الذاكرة المؤقّتة، والذاكرة المؤقّتة بالكتابة اللاحقة، وعرض ذاكرة النظام المؤقّتة بحجم 256 كيلوبايت، والقراءة المسبقة، والكتابة المتأخّرة عبر lazy writer، وفقدان الصفحات المتّسخة عند انقطاع الكهرباء، والكتابة المضمونة بـ FlushFileBuffers وFILE_FLAG_WRITE_THROUGH وFILE_FLAG_NO_BUFFERING، ومتطلّبات المحاذاة، والاتّساق مع الملفّ المعيَّن في الذاكرة، وFast I/O
cache_manager["مدير الذاكرة المؤقّتة"]
write_behind_caching["ذاكرة مؤقّتة بالكتابة اللاحقة (أسلوب الكتابة المتأخّرة)"]
memory_mapped_file["تعيين الملفّ (ملفّ معيَّن في الذاكرة)"]
system_cache_view["عرض ذاكرة النظام المؤقّتة (فتحة 256 كيلوبايت)"]
file_object["كائن الملفّ"]
lazy_writer["lazy writer (مؤشّر ترابط الكتابة المتأخّرة)"]
unflushed_write_loss["فقدان بيانات غير منعكسة"]
temp_file_attribute["FILE_ATTRIBUTE_TEMPORARY"]
dirty_page["صفحة متّسخة"]
power_loss["انقطاع الكهرباء أو انهيار نظام التشغيل"]
read_ahead["قراءة مسبقة (read-ahead)"]
sequential_scan_hint["FILE_FLAG_SEQUENTIAL_SCAN"]
random_access_hint["FILE_FLAG_RANDOM_ACCESS"]
flushfilebuffers["FlushFileBuffers"]
write_through["FILE_FLAG_WRITE_THROUGH"]
no_buffering["FILE_FLAG_NO_BUFFERING"]
sector_alignment_requirement["متطلّب محاذاة القطاع"]
invalid_parameter_error["ERROR_INVALID_PARAMETER (87)"]
frequent_durable_write["ديمومة مضمونة عند الكتابة المتكرّرة"]
flushviewoffile["FlushViewOfFile"]
fast_io["Fast I/O"]
procmon["Process Monitor (procmon.exe)"]
irp["IRP (حزمة طلب إدخال/إخراج)"]
synchronous_io["إدخال/إخراج تزامنيّ"]
cache_manager -->|"ينفّذ"| write_behind_caching
write_behind_caching -->|"يستخدم"| memory_mapped_file
cache_manager -->|"يستخدم"| system_cache_view
system_cache_view -->|"يستخدم"| memory_mapped_file
cache_manager -->|"يستخدم"| file_object
cache_manager -->|"يستخدم"| lazy_writer
lazy_writer -->|"يؤتمت"| write_behind_caching
lazy_writer -->|"يخفّف"| unflushed_write_loss
temp_file_attribute -.->|"لا يتوافق مع"| lazy_writer
write_behind_caching -->|"قد يسبّب"| dirty_page
dirty_page -->|"يُحفظ في"| system_cache_view
power_loss -.->|"قد يسبّب"| unflushed_write_loss
cache_manager -.->|"يمنع"| unflushed_write_loss
cache_manager -->|"يستخدم"| read_ahead
read_ahead -.->|"يُكوَّن بـ"| sequential_scan_hint
read_ahead -.->|"يُكوَّن بـ"| random_access_hint
flushfilebuffers -->|"يمنع"| unflushed_write_loss
write_through -->|"يمنع"| unflushed_write_loss
no_buffering -.->|"يخفّف"| unflushed_write_loss
no_buffering -->|"يشترط"| sector_alignment_requirement
sector_alignment_requirement -->|"يمنع"| invalid_parameter_error
no_buffering -->|"قد يسبّب"| invalid_parameter_error
flushfilebuffers -->|"غير موصى به لـ"| frequent_durable_write
no_buffering -->|"موصى به لـ"| frequent_durable_write
write_through -->|"موصى به لـ"| frequent_durable_write
no_buffering -->|"لا يتوافق مع"| system_cache_view
flushviewoffile -->|"يشترط"| memory_mapped_file
flushviewoffile -->|"ينبغي أن يسبق"| flushfilebuffers
fast_io -->|"يُتحقّق بـ"| procmon
fast_io -->|"يشترط"| system_cache_view
fast_io -->|"لا يتوافق مع"| irp
fast_io -->|"يشترط"| synchronous_io
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 32، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. هويّة الذاكرة المؤقّتة ── الملفّ يُعيَّن في الذاكرة
2.1. فتحات 256KB ونسخ الذاكرة
إن تصوّرتم ذاكرة ملفّات Windows المؤقّتة «وعاء كتل قرص»، صار كثير من سلوكها غير قابل للتفسير. الصورة الصحيحة هكذا: يعيّن مدير الذاكرة المؤقّتة مقاطع الملفّ بوحدات 256KB في «فتحات» فضاء عناوين النظام، والقراءة والكتابة وذاكرة مؤقّتة مفعَّلة تُنفَّذان كنسخ ذاكرة بين تلك الفتحة ومخزن التطبيق المؤقّت.1
flowchart TB
subgraph U["التطبيق (وضع المستخدم)"]
BUF["مخزن التطبيق المؤقّت<br/>(المنطقة الممرَّرة إلى ReadFile/WriteFile)"]
end
subgraph S["فضاء عناوين النظام"]
SLOT["ذاكرة الملفّات المؤقّتة في النظام<br/>فتحة عيّنت مقطع 256KB من الملفّ"]
end
DISK[("الملفّ على القرص")]
BUF <-->|"ReadFile/WriteFile =<br/>نسخ ذاكرة من الفتحة وإليها"| SLOT
SLOT <-->|"القراءة عند أوّل وصول و<br/>الكتابة اللاحقة تتمّان صفحة بصفحة"| DISK
الشكل 1: الصورة الحقيقيّة للإدخال/الإخراج وذاكرة مؤقّتة مفعَّلة. «قراءة الملفّ وكتابته» من منظور التطبيق في كثير من الحالات مجرّد نسخ ذاكرة
موضع يسهل فيه الخطأ. 256KB هي حبيبات العرض (التعيين)، لا تعني أنّ إدخال/إخراج القرص يجري دائماً بوحدات 256KB. صفحات داخل الفتحة تُقرأ حسب الحاجة، وكمّيّة الإدخال/الإخراج التي تطير فعلاً إلى القرص تتغيّر بحجم الطلب ونمط الوصول. إن كان المقطع يُقرأ لأوّل مرّة، يحدث إدخال/إخراج قرص لملئه (هنا يطير IRP الجزء 1 نزولاً في مكدّس التخزين). إن كان في الذاكرة المؤقّتة أصلاً، تكتمل القراءة بالنسخ وحده. «إصابة الذاكرة المؤقّتة تكتمل تزامنيّاً حتّى مع الإصدار اللاتزامنيّ» الذي رأيناه في الفصل 5 من الجزء 2 تجلٍّ لهذه الحركة: «الطلب الذي يمكن جوابه فوراً يُكمَل في مكانه». والعكس: إن بقيت الذاكرة المؤقّتة مفعَّلة والصفحة غير موجودة في الذاكرة، فلا آليّة لاتزامنيّة لمعالجة خطأ الصفحة، وقد تُعالَج قراءة لاتزامنيّة تزامنيّاً — الفخّ الذي رأيناه أيضاً في الجزء 2.7
2.2. هويّة «الذاكرة الحرّة نقصت»
استخدام الذاكرة المؤقّتة وحالة القراءة الاستباقيّة يُداران حسب طريقة الفتح (لكلّ كائن ملفّ)،1 لكن بيانات الذاكرة المؤقّتة نفسها مشتركة لكلّ ملفّ (دفق). مهما فتحتم الملفّ نفسه مرّات، لا تُصنع ذاكرات مؤقّتة منفصلة — من أيّ مقبض تُرى محتويات الذاكرة المؤقّتة نفسها (أساس الاتّساق في الفصل 6). الذاكرة المؤقّتة تعمل تحت قيادة مدير الذاكرة المؤقّتة طوال حياة Windows.1 إن نسختم ملفّاً كبيراً أو أكثرتم القراءة والكتابة، تُحوَّل الذاكرة المادّيّة الفارغة تدريجيّاً إلى ذاكرة مؤقّتة. حتّى إن بدا في مدير المهامّ أنّ الذاكرة الحرّة نقصت، فكثير منها «ذاكرة انتظار تُسلَّم سريعاً إن طلبها تطبيق، مستخدمة استخداماً ذا قيمة». كي لا يُخطَأ هذا التمييز في تشخيص نقص الذاكرة، عالجنا ممارسة الرصد أيضاً في «التمييز بين انتظار GC وتسريب الذاكرة في .NET».
هذه الحركة تُرى على الشاشة. افتحوا مدير المهامّ > الأداء > الذاكرة، فيُقسَم شريط «تكوين الذاكرة» في الأسفل إلى قيد الاستخدام / معدَّلة / الاستعداد / حرّة. معظم ذاكرة الملفّات المؤقّتة يدخل الاستعداد هذا، وفي القائمة اليمنى يُجمَع «مخزَّناً مؤقّتاً». للتفاصيل أدقّ، مراقب الموارد > علامة تبويب الذاكرة يعرض التقسيم نفسه بأحجام. انسخوا ملفّاً بعدّة غيغابايتات ثمّ انظروا ثانية: الاستعداد يزيد والحرّة تنقص، و«قيد الاستخدام» لا يكاد يتغيّر — أي ليس «الذاكرة أُكلت»، بل «الذاكرة الفارغة استُخدمت ذاكرةً مؤقّتة»، يُرى بالعين.
3. القراءة الاستباقيّة ── المضاربة على القراءة
مدير الذاكرة المؤقّتة، من أنماط الوصول السابقة، يقرأ مسبقاً المقطع الذي يبدو أنّه سيُقرأ تالياً (read-ahead). في ملفّ يُقرأ بالترتيب، تكون بيانات التتمّة في الذاكرة المؤقّتة قبل أن يطلبها التطبيق — هذا سرّ سرعة القراءة التسلسليّة. كمّيّة القراءة الاستباقيّة ليست ثابتة؛ تتغيّر حسب النمط المكتشف وحجم الطلب.
flowchart LR
A["تاريخ طلبات قراءة التطبيق<br/>يقرأ بالتتابع من البداية"]
D{"مدير الذاكرة المؤقّتة<br/>يكتشف النمط"}
R["قراءة استباقيّة: مقطع التتمّة<br/>يُحمَّل قبل الطلب<br/>(الكمّيّة تتغيّر حسب النمط وحجم الطلب)"]
H1["تلميح FILE_FLAG_SEQUENTIAL_SCAN<br/>= قراءة استباقيّة أجرأ"]
H2["تلميح FILE_FLAG_RANDOM_ACCESS<br/>= كبح القراءة الاستباقيّة لأنّها هدر"]
A --> D
D --> R
H1 -.-> D
H2 -.-> D
الشكل 2: القراءة الاستباقيّة. فضلاً عن اكتشاف نمط الوصول، يمكن إعطاء تلميح بأعلام CreateFile
FileOptions.SequentialScan / RandomAccess اللذان وردا في جدول التطابق في الجزء 1 تلميحان لمحرّك القراءة الاستباقيّة هذا. للأعمال الدفعيّة التي «تلتهم الكلّ بالترتيب» الأوّل، ولوصول يتنقّل كتتبع فهرس الثاني — أعلام يُخبَر بها نظام التشغيل مستقبلاً لا يعرفه إلا التطبيق، بهذا يتّضح موضع الاستخدام.
4. الكتابة المؤجَّلة ── معنى «نجاح» WriteFile
4.1. lazy writer يأتي كلّ ثانية
جانب الكتابة ذاكرة مؤقّتة بكتابة لاحقة (write-back). WriteFile يُرجع النجاح في لحظة نسخ البيانات إلى الفتحة، وانعكاس ذلك على القرص يُؤجَّل. سياسة «الكتابة لاحقاً» هذه هي الكتابة المؤجَّلة (lazy writing).1
من يتولّى الانعكاس lazy writer الذي يشغّله مدير الذاكرة المؤقّتة كلّ ثانية. يضع في طابور الكتابة ثُمن الصفحات التي لم تُفرَّغ مؤخّراً، وإن كان ثمّة ما يُكتب أكثر زاد التحميل. الملفّات المؤقّتة المنشأة بسمة FILE_ATTRIBUTE_TEMPORARY تُستثنى من هدف تفريغ lazy writer — لا معنى لكتابة ما يُفترض حذفه وشيكاً.1 غير أنّ هذا تلميح بالسمة: عند ضيق الذاكرة قد تُكتب لاحقاً على أيّ حال، ولا ينطبق على ملفّ «يشبه اسمه المؤقّت فحسب».
sequenceDiagram
participant App as التطبيق
participant C as ذاكرة النظام المؤقّتة
participant LW as lazy writer (يُشغَّل كلّ ثانية)
participant D as القرص
App->>C: WriteFile(البيانات)
Note over C: يُنسَخ إلى الفتحة<br/>وتُعلَّم الصفحة متّسخة (لم تُكتب بعد)
C-->>App: يُرجع TRUE فوراً
Note over App,C: من هنا حتّى الكتابة اللاحقة «نافذة الخطر»<br/>انقطاع الطاقة أو انهيار نظام التشغيل يفقد هذه البيانات
LW->>C: يختار 1/8 من الصفحات المتّسخة
LW->>D: يكتبها لاحقاً دفعة واحدة
Note over D: هنا أوّل ما تصير دائمة
الشكل 3: الكتابة المؤجَّلة. نجاح WriteFile يعني «سُلِّم إلى نظام التشغيل»، لا «صار دائماً»
4.2. إن حدث كذا، فإلى أيّ حدّ يختفي
نضبط معنى «نافذة الخطر» بدقّة. المصير ينقسم حسب نوع العطل.
flowchart TB
W["البيانات فور نجاح WriteFile<br/>(صفحة متّسخة في الذاكرة المؤقّتة)"]
Q{"ماذا حدث"}
A1["عمليّة التطبيق<br/>انهارت / أُنهيت قسراً"]
A2["توقّف نظام التشغيل كلّه<br/>(انقطاع طاقة، شاشة زرقاء)"]
S["البيانات تبقى<br/>الذاكرة المؤقّتة ملك نظام التشغيل لذا<br/>يكتبها lazy writer لاحقاً حسب الجدول"]
L["الصفحات المتّسخة تُفقد<br/>لا يبقى إلا ما كان قد وصل إلى القرص"]
W --> Q
Q --> A1
Q --> A2
A1 --> S
A2 --> L
الشكل 4: نوع العطل ومفترق البقاء. الذاكرة المؤقّتة ليست «ملك العمليّة» بل «ملك نظام التشغيل»
- موت التطبيق لا يُميت البيانات. من لحظة اكتمال النسخ إلى الذاكرة المؤقّتة، مالك البيانات نظام التشغيل. «حُفظ ثمّ سقط التطبيق فوراً والملفّ سليم» بفضل هذا.
- موت نظام التشغيل كلّه يُميت الجزء المتّسخ. تواتر التفريغ يُضبَط مقايضة بين الأداء والموثوقيّة، والوثائق تنصّ صراحة: «إن حدث فقدان مفاجئ للطاقة، تُفقد البيانات المخزَّنة مؤقّتاً».1
أي أنّ سؤال تصميم تطبيق الأعمال: «هل يُقبل فقدان هذه البيانات في لحظة انقطاع الطاقة؟». بضع ثوانٍ من السجلّ قد تُقبل. سجلّ طلب مؤكَّد لا يُقبل على الأرجح. الأدوات في الفصل التالي تُستخدم فقط لما لا يُقبل.
5. صندوق أدوات «كُتب يقيناً»
5.1. FlushFileBuffers ── اكتبه الآن حتّى النهاية
FlushFileBuffers يكتب إلى الجهاز بيانات الملفّ المحدَّد المخزَّنة مؤقّتاً حتّى نهايتها. بيانات نظام الملفّات الوصفيّة تُخزَّن مؤقّتاً دائماً، لذا لإيصال البيانات الوصفيّة نفسها يقيناً يلزم تفريغ (أو WRITE_THROUGH) — موضع يُمسَك أيضاً.12 في .NET يعادله FileStream.Flush(true) (Flush() وحده يسلّم مخزن .NET الداخليّ المؤقّت إلى نظام التشغيل، وذاكرة نظام التشغيل المؤقّتة تبقى كما هي).8
غير أنّ الوثائق الرسميّة تدقّ المسمار بوضوح: استدعاؤه في كلّ كتابة غير كفء. إن لزمَت الديمومة في كلّ واحدة من كتابات كثيرة، فاستخدموا NO_BUFFERING مع WRITE_THROUGH الآتي ذكرهما.2
5.2. FILE_FLAG_WRITE_THROUGH ── إزالة التأخير وحده
بالفتح بـ FILE_FLAG_WRITE_THROUGH تُكتب البيانات إلى الذاكرة المؤقّتة، وإلى القرص فوراً دون انتظار lazy writer.1 النقطة أنّ القراءة تظلّ تستفيد من الذاكرة المؤقّتة — جواب مباشر لـ«لتبقَ القراءة سريعة، وأزيلوا تأخير الكتابة وحده».
5.3. FILE_FLAG_NO_BUFFERING ── بلا مرور على الذاكرة المؤقّتة
FILE_FLAG_NO_BUFFERING يُخرج ذاكرة النظام المؤقّتة نفسها من القراءة والكتابة. كلّ قراءة وكتابة تصير إدخال/إخراجاً إلى جهاز القرص في كلّ مرّة، بلا مرور على الذاكرة المؤقّتة.1 غير أنّ ما يُتجاوز هو ذاكرة Windows المؤقّتة في النظام فحسب، وكما في الشكل 5 ذاكرة الكتابة داخل الجهاز طبقة أخرى. إن طُلبت مقاومة انقطاع الطاقة أيضاً، يظلّ الجمع مع WRITE_THROUGH أو FlushFileBuffers لازماً. أداة للنقل الكمّيّ لكمّ كبير، ولمحرّك قاعدة بيانات يدير مخازنه بنفسه، لكنّها تأتي بعقد صارم.3
- حجم القراءة والكتابة وإزاحة الملفّ مضاعف صحيح لحجم قطاع المجلّد (لقطاع 512 بايت: 512، 1024، 1536…).
- عنوان المخزن المؤقّت محاذٍ أيضاً لحجم القطاع المادّيّ (يلزم الاعتبار أيضاً لأقراص «Advanced Format» ذات قطاع مادّيّ 4096 بايت).
- ومع ذلك تظلّ البيانات الوصفيّة تُخزَّن مؤقّتاً، لذا للديمومة الكاملة يلزم الجمع مع WRITE_THROUGH أو
FlushFileBuffers.12
هذا «العقد» أوّل موضع يسقط فيه من اكتفى بإضافة العلم. القراءة والكتابة بلا احترام المحاذاة تفشلان بـ ERROR_INVALID_PARAMETER (87). ثلاث نقاط يجب استيفاؤها.3
| ما يُحاذى | الشرط | كيف يُستوفى |
|---|---|---|
| حجم القراءة والكتابة | مضاعف صحيح لحجم قطاع المجلّد | أخذ lpBytesPerSector من GetDiskFreeSpace والتقريب إلى مضاعفه |
| إزاحة الملفّ | نفسه (وكذلك عند تعيين Offset في OVERLAPPED) |
التقدّم بخطوات مضاعفة لحجم القطاع |
| عنوان المخزن المؤقّت | محاذاة على حجم القطاع المادّيّ | التخصيص بـ VirtualAlloc (يُرجع منطقة محاذاة على حدود الصفحة، عادة 4096 بايت) |
الثالثة الأكثر إهمالاً. العنوان الذي يرجعه malloc أو new أو مصفوفة C# لا يضمن المحاذاة على حدود القطاع. VirtualAlloc الذي يخصّص على حدود الصفحة يستوفي في الوقت نفسه شرط أقراص «Advanced Format» ذات قطاع مادّيّ 4096 بايت. الشكل الأدنى كالتالي.
// C++ / Win32. معالجة الأخطاء مخفَّضة إلى الحدّ الأدنى
DWORD sectorsPerCluster = 0, bytesPerSector = 0, freeClusters = 0, totalClusters = 0;
if (!GetDiskFreeSpaceW(L"C:\\", §orsPerCluster, &bytesPerSector,
&freeClusters, &totalClusters))
{
return GetLastError();
}
// جعل وحدة القراءة/الكتابة مضاعفاً صحيحاً لحجم القطاع (هنا ما يعادل 1MiB تقريباً)
const DWORD chunk = (1024 * 1024 / bytesPerSector) * bytesPerSector;
// المخزن المؤقّت منطقة محاذاة على حدود الصفحة (malloc/new لا يضمنان ذلك)
BYTE* buffer = static_cast<BYTE*>(
VirtualAlloc(nullptr, chunk, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
if (buffer == nullptr) { return GetLastError(); }
HANDLE h = CreateFileW(L"C:\\temp\\big.bin", GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, nullptr);
if (h == INVALID_HANDLE_VALUE)
{
// GetLastError يُرجع نتيجة «آخر استدعاء Win32». إن استُدعي VirtualFree
// أوّلاً، فقد يُستبدل سبب فشل CreateFileW الحقيقيّ (رفض الوصول، مسار
// غير موجود، إلخ) بنتيجة التنظيف، فلا يبقى إلا رمز بلا سبب مفهوم
const DWORD err = GetLastError();
VirtualFree(buffer, 0, MEM_RELEASE);
return err;
}
// التقدّم كلّ مرّة بوحدات chunk بايت يحفظ محاذاة الحجم والإزاحة كليهما
DWORD read = 0;
while (ReadFile(h, buffer, chunk, &read, nullptr) && read > 0)
{
// معالجة البايتات read الأولى من buffer
// (عند نهاية الملفّ يصير read < chunk. هذا طبيعيّ)
}
CloseHandle(h);
VirtualFree(buffer, 0, MEM_RELEASE);
كذلك، ليس في FileOptions لـ .NET قيمة تقابل FILE_FLAG_NO_BUFFERING. إن لزم حقّاً فستستدعون CreateFile مباشرة، وحينها أيضاً عليكم استيفاء شروط المحاذاة أعلاه بأنفسكم. ترتيب النظر: «NO_BUFFERING لأنّي أدير المخازن بنفسي»، لا «NO_BUFFERING لأنّي أريد سرعة أكبر».
5.4. ترتيب الاختيار
flowchart TB
A["مخزن التطبيق المؤقّت"]
B["ذاكرة الملفّات المؤقّتة في النظام<br/>(صفحات متّسخة)"]
C["الذاكرة المؤقّتة داخل جهاز القرص"]
D[("وسيط تخزين غير متطاير")]
A -->|"WriteFile الافتراضيّ: يُرجع النجاح عند الوصول إلى هنا"| B
B -->|"lazy writer (كلّ ثانية) / WRITE_THROUGH (فوراً)"| C
C -->|"توقيت الجهاز /<br/>FlushFileBuffers يطلب كتابة كاملة"| D
A -.->|"NO_BUFFERING يتجاوز الذاكرة المؤقّتة ويذهب مباشرة"| C
الشكل 5: طبقات البيانات، وإلى أيّ حدّ يدفع كلّ أداة. لا تنسوا الطبقة الأخيرة «الذاكرة المؤقّتة داخل جهاز القرص»
| الطريقة | ماذا يحدث | المشهد المناسب |
|---|---|---|
| الافتراضيّ (ذاكرة مؤقّتة مفعَّلة) | يكتمل بنسخ الذاكرة المؤقّتة. الانعكاس يتولاّه lazy writer | معظم إدخال/إخراج الملفّات |
FlushFileBuffers / Flush(true) |
يكتب بيانات تلك اللحظة + البيانات الوصفيّة حتّى النهاية | التثبيت عند منعطف (تثبيت معاملة مثلاً) |
FILE_FLAG_WRITE_THROUGH |
إلى القرص فوراً في كلّ كتابة (القراءة من الذاكرة المؤقّتة) | سجلّ أو دفتر لا يمكن فقدان كتاباته المتتالية |
FILE_FLAG_NO_BUFFERING (+WRITE_THROUGH) |
بلا مرور على الذاكرة المؤقّتة. شروط محاذاة | مخازن تُدار ذاتيّاً، إدخال/إخراج كمّيّ |
ترتيب الاختيار درجتان: أوّلاً قرّروا كم يمكن فقدانه في لحظة انقطاع الطاقة، ثمّ تأكّدوا كم يُقبل من البطء لأجل ذلك. لا تتصفّحوا الجدول من أعلى إلى أسفل — اسلكوا هذا المفترق.
flowchart TB
S["على وشك كتابة هذه البيانات"]
Q1{"هل يُقبل فقدانها في لحظة<br/>انقطاع الطاقة أو الشاشة الزرقاء"}
A0["البقاء على الافتراضيّ (الذاكرة المؤقّتة مفعَّلة)<br/>الأسرع. معظم الإدخال/الإخراج هنا"]
Q2{"ما لا يمكن فقدانه<br/>«منعطف» أم «كلّ سجلّ»"}
A1["FlushFileBuffers عند كلّ منعطف<br/>Flush(true) في .NET<br/>الكلفة: الانتظار عند المنعطف فقط"]
Q3{"هل تديرون المخازن بأنفسكم و<br/>تستوفون شروط المحاذاة في 5.3"}
A2["FILE_FLAG_WRITE_THROUGH<br/>كتابة فوريّة إلى القرص في كلّ كتابة<br/>القراءة تبقى سريعة عبر الذاكرة المؤقّتة"]
A3["FILE_FLAG_NO_BUFFERING<br/>+ FILE_FLAG_WRITE_THROUGH<br/>شكل «الديمومة المتكرّرة» الذي تذكره الوثائق الرسميّة"]
S --> Q1
Q1 -->|"يُقبل<br/>(سجلّ الثواني الأخيرة مثلاً)"| A0
Q1 -->|"لا يُقبل"| Q2
Q2 -->|"منعطف<br/>(تثبيت معاملة مثلاً)"| A1
Q2 -->|"كلّ سجلّ"| Q3
Q3 -->|"لا (تطبيق عاديّ)"| A2
Q3 -->|"نعم (محرّك قاعدة بيانات وغيرها)"| A3
الشكل 6: كيفيّة اختيار الأداة. المفترق الأوّل مطلب الموثوقيّة، والثاني الكلفة التي يمكن دفعها. لا مسار لـ«FlushFileBuffers في كلّ سجلّ»، لأنّ الوثائق الرسميّة، كما في 5.1، تعدّه غير كفء
ثلاثة أنماط عمليّة أيضاً.
- «الكتابة إلى ملفّ مؤقّت → تفريغ → إعادة تسمية» الوصفة لعدم ترك ملفّ نصف مكتوب. اكتبوا المحتوى حتّى النهاية ثمّ ثبّتوا بالاسم — هذا التسليم الذرّيّ عولج تفصيلاً في «التحكّم الآمن في تزامن تكامل الملفّات».
- إيكال الأمر لقاعدة البيانات تصميم مكتمل أيضاً. كيف يبني SQLite الديمومة من WAL والتفريغ في «استخدام SQLite في تطبيقات C# للأعمال». خيار «لا أكتب استراتيجيّة تفريغ بنفسي» دائماً على الطاولة.
- في المعيار، اشبهوا في الذاكرة المؤقّتة. قياس «القراءة أسرع ممّا ينبغي» يلتقط عادة إصابة الذاكرة المؤقّتة من المرّة الثانية فصاعداً. آداب القياس في «كيف تقارن إصدارات البرامج على Windows دون قياس الشيء الخطأ».
ولا تنسوا الطبقة الأخيرة في الشكل 5 — الذاكرة المؤقّتة داخل جهاز القرص. FlushFileBuffers يطلب كتابة كاملة تشملها، لكن في ذاكرة USB والأقراص الخارجيّة تتدخّل سياسة ذاكرة الكتابة لدى الجهاز («الإزالة السريعة» و«أداء أفضل»). التعامل مع الأجهزة القابلة للإزالة أيضاً في «كيفيّة التعامل مع أجهزة USB من تطبيق Windows».
6. الاتّساق مع الملفّ المعيَّن في الذاكرة
في الجزء 1، حين سمعتم «كيان الذاكرة المؤقّتة تعيين ملفّ»، لا بدّ أنّ أحداً فكّر: إذن العرض الذي صنعته بنفسي بـ MapViewOfFile، أيتشاجر مع ذاكرة ReadFile/WriteFile المؤقّتة؟
لا يتشاجر. لأنّهما يركبان الآليّة نفسها. كائن تعيين الملفّ مسنود بملفّ، وطرد الصفحة يُنفَّذ ككتابة لاحقة إلى الملفّ. حتّى إن صنعت عدّة عمليّات عروضاً للملفّ المحلّيّ نفسه، المحتوى المرئيّ متماسك (متّسق).4
flowchart TB
subgraph P1["فضاء عناوين العمليّة A"]
V1["عرض MapViewOfFile"]
end
subgraph SYS["فضاء عناوين النظام"]
SC["عرض مدير الذاكرة المؤقّتة<br/>(الفتحة التي يستخدمها ReadFile/WriteFile)"]
end
PAGES["صفحات مادّيّة واحدة<br/>(ذاكرة مسنودة بالملفّ)"]
DISK[("الملفّ على القرص")]
V1 --> PAGES
SC --> PAGES
PAGES --> DISK
NB["إدخال/إخراج FILE_FLAG_NO_BUFFERING<br/>خارج هذا التشارك (مباشرة إلى القرص)"]
NB -.-> DISK
الشكل 7: عرض التعيين والذاكرة المؤقّتة كلاهما ينظران إلى «الصفحات المسنودة بالملفّ» نفسها. خارج الدائرة NO_BUFFERING وحده
تنبيهان.
- إدخال/إخراج
FILE_FLAG_NO_BUFFERINGخارج هذا الاتّساق. القراءة والكتابة اللتان لا تمرّان بالذاكرة المؤقّتة لا تُطابقان المحتوى عبر العرض / الذاكرة المؤقّتة. إن خلطتم فعليكم أخذ الاتّساق بأنفسكم. - إدامة عرض التعيين درجتان.
FlushViewOfFileيبدأ كتابة الصفحات المتّسخة في النطاق، لكنّه لا يكتب البيانات الوصفيّة، ولا ينتظر الكتابة المادّيّة من ذاكرة جهاز القرص. للوصول يقيناً، استدعواFlushFileBuffersبعدFlushViewOfFile.5
ممارسة تعيين الملفّ كذاكرة مشتركة (التشارك المسمّى، والمزامنة، وأنماط الحوادث) في «المزالق وأفضل الممارسات عند استخدام shared memory».
7. الإدخال/الإخراج السريع ── استرجاع واجب الجزء 1
أوّلاً في سطرين. الإدخال/الإخراج السريع طريق مختصر أُعدّ للقراءة والكتابة التزامنيّتين على ملفّ في الذاكرة المؤقّتة أصلاً؛ يتبادل البيانات مع الذاكرة المؤقّتة مباشرة دون تجميع IRP (حزمة طلب إدخال/إخراج — الوعاء الذي تسلّم به النواة الطلب إلى برنامج التشغيل). اختلاط IRP_MJ_READ وFASTIO_READ في عمود Operation في Procmon فرق ما إذا سلك «القراءة» نفسها المسار العاديّ أم المختصر.
في القسم 5.2 من الجزء 1 كتبنا «ليس كلّ إدخال/إخراج يصير IRP». هذا الجواب.
لقراءة ملفّ في الذاكرة المؤقّتة وكتابته، من المعلوم أنّ الأمر يكتمل بنسخ ذاكرة مع الذاكرة المؤقّتة، دون تجميع IRP وإمراره في مكدّس الأجهزة. لذا توفّر Windows، لـالإدخال/الإخراج التزامنيّ على ملفّ مخزَّن مؤقّتاً، طريقاً مختصراً اسمه الإدخال/الإخراج السريع: لا يصنع IRP، يستدعي «نقاط دخول الإدخال/الإخراج السريع» في نظام الملفّات مباشرة، وينسخ مباشرة من مدير الذاكرة المؤقّتة.6 إن تعذّرت المعالجة بالإدخال/الإخراج السريع (ليس في الذاكرة المؤقّتة، قفل متورّط، مرشّح يتدخّل، وغيرها) يعود الأمر إلى مسار IRP العاديّ. علماً أنّ هذا مسار سريع لطلب تزامنيّ تحديداً، لا «إصابة الذاكرة المؤقّتة = إدخال/إخراج سريع دائماً». عمليّات مقبض لاتزامنيّ (FILE_FLAG_OVERLAPPED) قد تُعالَج بمسار IRP حتّى حين تكتمل في مكانها من الذاكرة المؤقّتة (الفصل 5 من الجزء 2).
flowchart TB
REQ["قراءة وكتابة تزامنيّتان على مقبض بذاكرة مؤقّتة مفعَّلة"]
Q{"هل يستطيع الإدخال/الإخراج السريع معالجته<br/>(موجود في الذاكرة المؤقّتة مثلاً)"}
FAST["الإدخال/الإخراج السريع<br/>نسخ مباشر مع الذاكرة المؤقّتة بلا IRP<br/>يظهر FASTIO_ في Procmon"]
IRP["المسار العاديّ<br/>يبني IRP ويرسله إلى مكدّس الأجهزة<br/>(عالم الشكل 6 في الجزء 1)"]
REQ --> Q
Q -->|يستطيع| FAST
Q -->|لا يستطيع| IRP
الشكل 8: مفترق الإدخال/الإخراج السريع. لذلك يظهر في Procmon خليط FASTIO_READ وIRP_MJ_READ
سبب اختلاط أسطر FASTIO_ في رصد Procmon في الفصل 7 من الجزء 1 يُفسَّر بهذا. لقراءة أصابت الذاكرة المؤقّتة، حتّى IRP يصير ترفاً. وجود هذا المسار يؤثّر أيضاً في برامج تشغيل المرشّح التي نعالجها في الجزء 6 (المرشّح المصغّر يستطيع التقاطع مع الإدخال/الإخراج السريع أيضاً).
8. الخلاصة
- ذاكرة ملفّات Windows المؤقّتة كتابة لاحقة، وكيانها تعيين مقاطع الملفّ بوحدات 256KB. القراءة والكتابة وذاكرة مؤقّتة مفعَّلة تصيران نسخ ذاكرة مع الفتحة.1
- القراءة تستبق بالمضاربة، و
SequentialScan/RandomAccessتلميحان لها.1 - الكتابة يلحقها lazy writer كلّ ثانية. إن مات التطبيق بقيت البيانات، وإن مات نظام التشغيل كلّه يختفي الجزء المتّسخ وحده. سؤال التصميم: «هل يُقبل فقدان هذه البيانات في لحظة انقطاع الطاقة؟»1
- أدوات الكتابة يقيناً
FlushFileBuffers(تثبيت عند منعطف) /WRITE_THROUGH(كلّ كتابة) /NO_BUFFERING(بلا مرور على الذاكرة المؤقّتة + شروط محاذاة). التفريغ في كلّ مرّة غير كفء؛ للديمومة المتكرّرة التوصية الرسميّة الجمع بين NO_BUFFERING وWRITE_THROUGH. وانتبهوا إلى أنّ البيانات الوصفيّة تُخزَّن مؤقّتاً دائماً.231 - عرض التعيين والذاكرة المؤقّتة يشتركان في الصفحات نفسها ويتّسقان. خارج الدائرة NO_BUFFERING وحده. إدامة التعيين درجتان:
FlushViewOfFileثمّFlushFileBuffers.45 - القراءة والكتابة التزامنيّتان اللتان تصيبان الذاكرة المؤقّتة تُسقِطان حتّى IRP بـالإدخال/الإخراج السريع. هويّة
FASTIO_التي رأيناها في Procmon الجزء 1.6
التتمّة الجزء 5 «أعماق إدخال/إخراج Windows (الجزء 5) ── البنية الداخليّة لـ NTFS: فهم نظام الملفّات من MFT». حتّى الآن عاملنا الملفّ «إزاحة وتسلسل بايتات»؛ من هنا ننزل إلى كيف يضع NTFS البيانات — MFT، وعدّة دفق بيانات، والسجلّ، والروابط الصلبة — البنية الساكنة على القرص نفسه.
مقالات ذات صلة
- أعماق إدخال/إخراج Windows (الجزء 1) ── كلّ قراءة وكتابة تصير IRP: الصورة الكلّيّة لنظام الإدخال/الإخراج
- أعماق إدخال/إخراج Windows (الجزء 2) ── الإدخال/الإخراج التزامنيّ واللاتزامنيّ: المعنى الحقيقيّ لـ OVERLAPPED
- أعماق إدخال/إخراج Windows (الجزء 3) ── منفذ إكمال الإدخال/الإخراج (IOCP) ومجمّع خيوط .NET: قبو async/await
- التحكّم الآمن في تزامن تكامل الملفّات - أفضل الممارسات لـ file lock والمطالبة الذرّيّة والمعالجة idempotent
- المزالق وأفضل الممارسات عند استخدام shared memory - تنظيم مسبق للتزامن، الرؤية، العمر، ABI، والأمان
- استخدام SQLite في تطبيقات C# للأعمال ── وضع WAL، والتحكّم الحصريّ، والوقاية من التلف، والتمييز عن EF Core
- كيف تقارن إصدارات البرامج على Windows دون قياس الشيء الخطأ
- كيفيّة التعامل مع أجهزة USB من تطبيق Windows ── اختيار COM الافتراضيّ وHID وWinUSB وSDK المتخصّص
مجالات الاستشارة ذات الصلة
تتعامل شركة كومورا سوفت ذ.م.م. مع تصميم إدخال/إخراج الملفّات في تطبيقات أعمال Windows والتحقيق في أعطاله — حالات مثل «بيانات ظننت أنّي حفظتها قد اختفت» أو «كتابة الملفّ بطيئة، أو سريعة بشكل مريب».
- تطوير تطبيقات Windows
- التحقيق في الأخطاء وتحليل الأسباب الجذريّة
- ترحيل الأصول القائمة والاستفادة منها
- تواصل معنا
روابط مرجعيّة
-
Microsoft Learn, File Caching. حول تخزين Windows لبيانات الملفّات مؤقّتاً افتراضيّاً، وجريان القراءة من ذاكرة الملفّات المؤقّتة في النظام والكتابة إليها أيضاً — ذاكرة مؤقّتة بكتابة لاحقة؛ وحول إدارة الذاكرة المؤقّتة لكلّ كائن ملفّ وعملها تحت قيادة مدير الذاكرة المؤقّتة؛ وحول تسمية سياسة تأخير الكتابة إلى القرص والإبقاء في الذاكرة المؤقّتة بالكتابة المؤجَّلة (lazy writing)؛ وحول قراءة مقطع 256KB عند قراءة الملفّ إلى فتحة 256KB في فضاء عناوين النظام، ونسخ عمليّة المستخدم البيانات من تلك الفتحة وإليها؛ وحول تشغيل مدير الذاكرة المؤقّتة lazy writer كلّ ثانية ووضع ثُمن الصفحات التي لم تُفرَّغ مؤخّراً في طابور الكتابة إلى القرص، وزيادة التحميل عند الحاجة؛ وحول عدم تفريغ الملفّات المؤقّتة؛ وحول فقدان بيانات الذاكرة المؤقّتة غير المكتوبة إن وقع عطل نظام مفاجئ مثل فقدان الطاقة؛ وحول إمكان بقاء بيانات الملفّ الوصفيّة مخزَّنة مؤقّتاً حتّى مع تعطيل الذاكرة المؤقّتة بـ FILE_FLAG_NO_BUFFERING؛ وحول كتابة البيانات مع FILE_FLAG_WRITE_THROUGH إلى الذاكرة المؤقّتة وإلى القرص فوراً بلا تأخير lazy writer؛ وحول تخزين بيانات نظام الملفّات الوصفيّة مؤقّتاً دائماً، لذا لإدامة البيانات الوصفيّة يلزم تفريغ أو FILE_FLAG_WRITE_THROUGH. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19
-
Microsoft Learn, FlushFileBuffers function. حول كتابة WriteFile عادة إلى مخزن داخليّ مؤقّت يخرجه نظام التشغيل دوريّاً إلى القرص؛ وحول إخراج FlushFileBuffers كلّ المعلومات المخزَّنة مؤقّتاً للملفّ المحدَّد إلى الجهاز؛ وحول أنّ استدعاءه في كلّ واحدة من كتابات كثيرة غير كفء، وأنّ التطبيقات التي تحتاج ديمومة بيانات مهمّة مع كتابات متكرّرة ينبغي أن تستخدم إدخالاً/إخراجاً بلا تخزين مؤقّت عبر FILE_FLAG_NO_BUFFERING وFILE_FLAG_WRITE_THROUGH؛ وحول أنّ الاستدعاء على مقبض مجلّد (بصلاحيّات مدير) يفرّغ كلّ الملفّات المفتوحة على ذلك المجلّد. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, File Buffering. حول شروط الوصول إلى ملفّ فُتح بـ FILE_FLAG_NO_BUFFERING: وجوب كون حجم القراءة والكتابة وإزاحة الملفّ (بما فيه التعيين عبر OVERLAPPED) مضاعفاً صحيحاً لحجم قطاع المجلّد؛ ووجوب محاذاة عنوان مخزن القراءة والكتابة المؤقّت على حجم القطاع المادّيّ؛ وضرورة الاعتبار لأجهزة Advanced Format ذات قطاع مادّيّ 4,096 بايت. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, File Mapping. حول استناد كائن تعيين الملفّ إلى ملفّ على القرص، وتنفيذ طرد الصفحة ككتابة المحتوى المتغيّر إلى ذلك الملفّ؛ وحول تماسك البيانات (محتوى مطابق لملفّ القرص) حين تصنع عدّة عمليّات عروضاً لملفّ محلّيّ من كائن تعيين الملفّ نفسه. ↩ ↩2 ↩3
-
Microsoft Learn, FlushViewOfFile function. حول بدء FlushViewOfFile كتابة الصفحات المتّسخة ضمن نطاق عرض التعيين إلى القرص؛ وحول أنّ هذه الدالّة لا تفرّغ بيانات الملفّ الوصفيّة ولا تنتظر اكتمال الكتابة المادّيّة من ذاكرة القرص العتاديّة؛ وحول وجوب استدعاء FlushFileBuffers بعد FlushViewOfFile لكتابة كلّ الصفحات المتّسخة والبيانات الوصفيّة مادّيّاً حتّى النهاية. ↩ ↩2 ↩3
-
Microsoft Learn, IRPs Are Different From Fast I/O. حول كون الإدخال/الإخراج السريع مساراً سريعاً لإدخال/إخراج تزامنيّ نحو ملفّ مخزَّن مؤقّتاً، يستدعي نقاط دخول نظام الملفّات ومدير الذاكرة المؤقّتة مباشرة بلا توليد IRP؛ وحول نقل البيانات مباشرة من الذاكرة المؤقّتة إلى مخزن المستخدم المؤقّت (أو العكس)؛ وحول استخدام مسار IRP العاديّ إن تعذّرت المعالجة بالإدخال/الإخراج السريع. ↩ ↩2 ↩3
-
Microsoft Learn, Asynchronous disk I/O appears as synchronous on Windows. حول اكتمال الطلب في مكانه وإرجاع TRUE إن كانت البيانات في الذاكرة المؤقّتة؛ وحول تنفيذ ذاكرة Windows المؤقّتة بتعيين ملفّ، وغياب آليّة خطأ صفحة لاتزامنيّة، لذا قد تُعالَج قراءة لاتزامنيّة وذاكرة مؤقّتة مفعَّلة تزامنيّاً إن لم تكن الصفحة موجودة. ↩
-
Microsoft Learn, FileStream.Flush method (.NET). حول إخراج Flush() مخزن الدفق الداخليّ المؤقّت إلى نظام التشغيل؛ وحول أنّ Flush(true) يفرّغ فوق ذلك كلّ مخازن الملفّات الوسيطة (مخازن نظام التشغيل) أيضاً. ↩
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
أعماق إدخال/إخراج Windows (الجزء 5) ── البنية الداخليّة لـ NTFS: فهم نظام الملفّات من MFT
الجزء 5 من سلسلة تشرح البنية الداخليّة لـ NTFS بالرسوم. نرتّب MFT وسجلّ الملفّ، وعدّة دفق بيانات (Zone.Identifier)، والروابط الصلبة وأسما...
أعماق إدخال/إخراج Windows (الجزء 6، الأخير) ── برامج تشغيل المرشّح والمرشّحات المصغّرة: لماذا يستطيع Procmon ومسح الفيروسات التقاطع مع الإدخال/الإخراج
الحلقة الأخيرة من سلسلة تشرح برامج تشغيل المرشّح والمرشّحات المصغّرة في Windows بالرسوم. نرتّب مدير المرشّحات والارتفاع، واستدعاءات pre/p...
أفضل الممارسات العمليّة لتعدّد مؤشّرات الترابط: نسخة .NET ── ما تقرِّره قبل إضافة مؤشّرات ترابط
قواعد تصميم عمليّة تمنع شيفرة .NET/C# متعدّدة مؤشّرات الترابط من الانهيار أو التجمّد أحياناً: اركب على Task بدل إنشاء المؤشّرات بنفسك، قل...
استخدام WMI/CIM من C# وPowerShell ── دليل عمليّ لاسترجاع معلومات العتاد ومراقبة العمليّات والاستعلام عن بُعد
الإجابة الكلاسيكيّة للحصول على الرقم التسلسليّ للحاسوب، ومراقبة المساحة الحرّة على القرص، وكشف بدء عمليّة هي WMI/CIM. يشرح المقال أوامر C...
MAX_PATH ومطبّات المسارات وأسماء الملفّات في Windows ── حدّ 260 حرفاً، الأسماء المحجوزة، النقطة الأخيرة، حساسيّة الأحرف
ننظّم مشكلات حدود المسارات وأسماء الملفّات، وهي سبب شائع لظهور «الملفّ غير موجود». نشرح تفاصيل MAX_PATH=260 حرفاً، وتفعيل المسارات الطويل...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- متى أرجع WriteFile نجاحاً، هل كُتبت البيانات على القرص؟
- افتراضيّاً لا. ذاكرة ملفّات Windows المؤقّتة من نوع الكتابة اللاحقة (write-back)، وWriteFile يُرجع النجاح في اللحظة التي نُسخت فيها البيانات إلى ذاكرة الملفّات المؤقّتة في النظام. الكتابة إلى القرص تتولاها لاحقاً lazy writer الذي يشغّله مدير الذاكرة المؤقّتة كلّ ثانية. المهمّ هو الفرق حسب نوع العطل. إن انهارت عمليّة التطبيق، فالبيانات التي دخلت الذاكرة المؤقّتة لا تُفقد، لأنّ نظام التشغيل يكتبها لاحقاً ما دام نظام التشغيل نفسه حيّاً. أمّا إن سقط نظام التشغيل كلّه — انقطاع طاقة، شاشة زرقاء — فتُفقد البيانات المتّسخة في الذاكرة المؤقّتة التي لم تُكتب بعد. القراءة الدقيقة لـ«نجاح WriteFile» ليست «صار دائماً» بل «سُلِّم إلى نظام التشغيل».
- كيف أضمن أن تصل البيانات فعلاً إلى القرص؟
- ثلاثة أدوات. الأوّل FlushFileBuffers، يكتب إلى الجهاز كلّ البيانات المخزَّنة مؤقّتاً والبيانات الوصفيّة لذلك الملفّ (في .NET يعادله FileStream.Flush(true)). الثاني FILE_FLAG_WRITE_THROUGH، يكتب في كلّ كتابة إلى الذاكرة المؤقّتة وإلى القرص فوراً في آن. الثالث FILE_FLAG_NO_BUFFERING، لا يمرّ على الذاكرة المؤقّتة أصلاً. وثائق Microsoft تنصّ على أنّ استدعاء FlushFileBuffers في كلّ كتابة غير كفء، وأنّ التطبيقات التي تحتاج ديمومة موثوقة مع كتابات متكرّرة ينبغي أن تجمع FILE_FLAG_NO_BUFFERING مع FILE_FLAG_WRITE_THROUGH. كلّ منها يتخلّى عن جزء من فائدة الذاكرة المؤقّتة فيصير أبطأ، لذا عمليّاً لا تُعلَّق على كلّ شيء، بل تُحصر في كتابات لا يمكن فقدانُها حقاً.
- ما الفرق بين FILE_FLAG_WRITE_THROUGH وFILE_FLAG_NO_BUFFERING؟
- WRITE_THROUGH يعني «نكتب إلى الذاكرة المؤقّتة، لكن نكتب إلى القرص أيضاً قبل الاكتمال». القراءة تظلّ تستفيد من الذاكرة المؤقّتة؛ يُزال تأخير الكتابة المؤجَّلة وحده. NO_BUFFERING يعني «القراءة والكتابة لا تمرّان بذاكرة النظام المؤقّتة»، فتصيران إدخال/إخراجاً إلى الجهاز في كلّ استدعاء (غير أنّ ما يُتجاوز هو ذاكرة Windows المؤقّتة فحسب، لا ذاكرة الكتابة داخل جهاز التخزين نفسه). في المقابل قيود صارمة: حجم كلّ قراءة أو كتابة وإزاحة الملفّ يجب أن يكونا مضاعفاً صحيحاً لحجم قطاع المجلّد، وعنوان المخزن المؤقّت نفسه يجب أن يُحاذى على حدود القطاع المادّيّ. كذلك، حتّى مع NO_BUFFERING تظلّ بيانات نظام الملفّات الوصفيّة تُخزَّن مؤقّتاً، لذا لإدامة البيانات الوصفيّة يلزم FlushFileBuffers أو WRITE_THROUGH فوق ذلك. الاستخدام النموذجيّ لبرمجيّات تدير مخازنها بنفسها مثل محرّك قاعدة بيانات؛ والتطبيق العاديّ يبدأ عادة بـ WRITE_THROUGH أو FlushFileBuffers.
- مدير المهامّ يُظهر ذاكرة حرّة قليلة — هل السبب ذاكرة الملفّات المؤقّتة؟
- في كثير من الحالات نعم، وهذا سلوك طبيعيّ. Windows تستخدم الذاكرة المادّيّة الفارغة بنشاط كذاكرة ملفّات مؤقّتة، فنسخ ملفّ كبير أو كثرة القراءة والكتابة ينفخان الذاكرة المؤقّتة فيبدو استخدام الذاكرة أعلى. غير أنّ كثيراً من الصفحات التي تستخدمها الذاكرة المؤقّتة من نوع يُستردّ بسرعة نسبيّة في اللحظة التي يطلب فيها تطبيق ذاكرة، وهذا يلزم تمييزه عن نفاد ذاكرة حقيقيّ. حين تشكّون في نقص حقيقيّ، من الأجدى النظر إلى الذاكرة الملتزَم بها وتكرار الأخطاء الصلبة، لا إلى كمّيّة الذاكرة الحرّة الظاهرة وحدها.
- إن لمست الملفّ نفسه عبر عرض معيَّن في الذاكرة وعبر ReadFile/WriteFile، أيختلّ المحتوى؟
- مع الإدخال/الإخراج العاديّ وذاكرة مؤقّتة مفعَّلة لا. ذاكرة Windows المؤقّتة نفسها منفَّذة كتعيين ملفّ، لذا يشترك عرض التعيين وذاكرة الملفّ المحلّيّ نفسه في البيانات نفسها — التغيير عبر أحدهما يظهر عبر الآخر. عدّة عمليّات تصنع عروضاً من كائن تعيين الملفّ نفسه ترى أيضاً بيانات متماسكة. غير أنّ قراءة مقبض فُتح بـ FILE_FLAG_NO_BUFFERING وكتابته يتجاوزان الذاكرة المؤقّتة ويخرجان عن هذا الاتّساق. كذلك، لإدامة تغييرات العرض المعيَّن يقيناً، لا يكفي FlushViewOfFile وحده — فهو لا يكتب البيانات الوصفيّة ولا ينتظر ذاكرة عتاد — فيلزم استدعاء FlushFileBuffers بعد FlushViewOfFile.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.