تعمل بوابات VPN للمؤسسات ومرحلات الحافة الموزعة الحديثة على خوادم متعددة الأنوية فائقة الكثافة مزودة بـ 64 أو 128 أو 256 خيط معالجة فيزيائي. وعند معالجة تدفقات شبكية مجمعة تصل إلى عشرات الغيغابت في الثانية عبر مئات الآلاف من الأنفاق المشفرة المتزامنة، نادرًا ما ترتبط قيود الإنتاجية بسرعة تردد المعالج المركزية. وبدلاً من ذلك، يحدث التراجع في الأداء نتيجة اختناقات حادة في النظام الفرعي للذاكرة: ارتداد سطور الذاكرة المخبأة (cache-line bouncing)، وتكدس البيانات على نواقل الاتصال البيني لعقد NUMA، وحمل المقاطعات بين المعالجات (IPI).
في الإعدادات القياسية لنواة لينكس، تخضع حزم البيانات الواردة عبر بطاقات الشبكة متعددة الصفوف (multi-queue NIC) لمعالجة المقاطعات البرمجية (softirq) على إحدى أنوية المعالج، ليتم بعد ذلك تسليمها إلى خيط معالجة في مساحة المستخدم يعمل على نواة معالج مختلفة تمامًا. يؤدي هذا الفصل المعماري إلى إبطال ذاكرة التخزين المؤقت L1 و L2 لكل حزمة بيانات، مما يجبر ناقل الذاكرة على مزامنة البيانات باستمرار عبر وحدات المعالجة المختلفة.
ولتحقيق توسع خطي متناسق مع عدد الأنوية والحفاظ على زمن استجابة متوقع يقل عن ميلي ثانية واحدة، تعتمد مرحلات الحافة عالية الكفاءة على خيارات مقابس متقدمة في لينكس: SO_INCOMING_CPU و SO_INCOMING_NAPI_ID. ومن خلال ربط توزيع المقابس مباشرة بصفوف الاستقبال المادية والنواة التي تنفذ مقاطعات softirq، تحقق هذه الآليات استقرارًا وتوطينًا كاملاً للذاكرة المخبأة دون الحاجة إلى اتصالات بينية معقدة بين العمليات، محولة خوادم التوجيه متعددة الأنوية إلى محركات توجيه فائقة التوازي وخالية تمامًا من أقفال التزامن.
جدار قابلية التوسع متعددة الأنوية: تضارب الذاكرة المخبأة وتكاليف المعالجات البينية
لفهم أسباب تراجع إنتاجية الوكلاء الشبكيين على الخوادم ذات الكثافة العالية للأنوية، يمكن تتبع المسار الفيزيائي الذي تسلكه حزمة البيانات عبر بنية الخادم:
- الاستقبال المادي (Ingress): تصل الحزمة المشفرة إلى بطاقة الشبكة. تقوم وحدة التوسيع من جانب الاستقبال (RSS) بحساب رمز Toeplitz وتوجيه الإطار إلى صف استقبال فيزيائي محدد (مثل صف RX رقم 3).
- المقاطعة وحلقة NAPI: تصدر بطاقة الشبكة مقاطعة MSI-X، لتوجيه مسار التنفيذ إلى النواة 3. تعمل حلقة NAPI الخاصة بالنواة ضمن المعالج
ksoftirqd/3لتحليل ترويسات الحزم والتحقق من المعايير التشفيرية وتحديد المقبس المستهدف. - سحب الحزمة واستدعاء مساحة المستخدم: عندما يعتمد وكيل مساحة المستخدم على حلقة epoll اعتيادية دون تحديد تقارب المعالجات، يستيقظ خيط عمل عشوائي يعمل على النواة 11 لقراءة الحزمة عبر استدعاء
recvmsg().
معضلة تضارب الذاكرة المخبأة (في غياب تقارب الأنوية):
بطاقة الشبكة (صف RSS رقم 3)
│ (مقاطعة MSI-X)
▼
[ النواة 3: حلقة Softirq / NAPI ] <── بيانات الحزمة كُتبت في ذاكرة L1/L2 للنواة 3
│
│ (تحويل بين الأنوية / إيقاظ Epoll)
▼
[ النواة 11: خيط عمل مساحة المستخدم ] <── إخفاق ذاكرة مخبأة بارد! قراءة إجبارية عبر ناقل
الذاكرة من النواة 3 أو من ذاكرة L3/RAM المشتركة.
عندما يطلب خيط المعالجة في النواة 11 قراءة البيانات والواصفات التي دونتها النواة 3، تضطر بنية المعالج إلى تشغيل بروتوكولات اتساق الذاكرة المخبأة (مثل MESI أو MOESI) عبر النواقل البينية أو وصلات AMD Infinity Fabric و Intel UPI. وفي خادم يعالج 5 ملايين حزمة في الثانية، يؤدي هذا الإلغاء المتبادل لذاكرة التخزين المؤقت إلى استنزاف النطاق الترددي للناقل الداخلي، مما يرفع زمن استجابة الذيل p99 بنسبة تتجاوز 400%.
نظرة متعمقة: SO_INCOMING_CPU وتوافق مقاطعات Softirq في النواة
يوفر خيار المقبس SO_INCOMING_CPU (الذي تم تقديمه في لينكس 3.19) للمطورين تحكمًا دقيقًا في آلية توجيه الاتصالات عبر أنوية المعالج. ويعمل هذا الخيار وفق نمطين: فحص بيانات المقبس وتوجيه الاتصالات الواردة.
1. توجيه الاتصالات مباشرة إلى الأنوية المعالجة للمقاطعات
عند تفعيل SO_INCOMING_CPU إلى جانب SO_REUSEPORT، يصبح اختيار المقبس حتميًا ومحددًا بدقة. وبدلاً من الاعتماد على التوزيع العشوائي التلقائي للنواة لطلبات المصافحة عبر المقابس المستمعة، يقوم التطبيق بإنشاء مقابس استماع مخصصة، بحيث يخصص مقبس لكل نواة معالج فيزيائية:
int cpu_id = 4;
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
int enable_reuseport = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &enable_reuseport, sizeof(enable_reuseport));
/* توجيه الاتصالات المعالجة في مقاطعات النواة 4 إلى هذا المقبس مباشرة */
setsockopt(listen_fd, SOL_SOCKET, SO_INCOMING_CPU, &cpu_id, sizeof(cpu_id));
bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr));
listen(listen_fd, 1024);
خلال وصول الحزم، تتحقق وظيفة البحث في النواة مما إذا كان معرف النواة الحالية (smp_processor_id()) يتطابق مع أي مقبس ضمن مصفوفة SO_REUSEPORT. وعند العثور على التطابق، يوضع الاتصال مباشرة في صف القبول الخاص بذلك المقبس تحديدًا.
2. التخلص من المقاطعات بين المعالجات وارتداد ذاكرة L3 المؤقتة
عندما تقيد خيوط العمل في مساحة المستخدم تنفيذها بالأنوية المتطابقة عبر pthread_setaffinity_np()، تقتصر عمليات القراءة على الصفوف المحلية:
- خيط العمل 4 مثبت للعمل على النواة 4.
- النواة 4 تعالج إطارات الشبكة الواردة ضمن سياق softirq.
- خيط العمل 4 يسحب البيانات من المقبس المعين بقيمة
SO_INCOMING_CPU = 4.
ونظرًا لبقاء البيانات داخل الذاكرة المخبأة L1 و L2 للنواة 4، ينخفض زمن الوصول إلى الذاكرة من نحو 60 نانوثانية (عند القراءة من ذاكرة L3 البعيدة أو عقدة NUMA أخرى) إلى أقل من 2 نانوثانية، وتختفي إشارات المقاطعة البينية (IPI) تمامًا من مسار معالجة البيانات.
محاذاة الصفوف المادية: الاستعلام عن SO_INCOMING_NAPI_ID
بينما يعالج SO_INCOMING_CPU التوافق على مستوى الأنوية، تقوم بطاقات الشبكة الحديثة بتوزيع الأعباء عبر صفوف عتادية متعددة، حيث يماثل كل صف عتادي مثيلاً مستقلاً لـ NAPI داخل النواة.
1. ربط حلقات استقبال بطاقة الشبكة بخيوط معالجة مساحة المستخدم
أضافت النواة لينكس 4.12 خيار المقبس للقراءة فقط SO_INCOMING_NAPI_ID. يتيح هذا الخيار لبرمجيات مساحة المستخدم الاستعلام عن المعرف الدقيق لصف الاستقبال العتادي الذي تسلم حزم الاتصال:
unsigned int napi_id = 0;
socklen_t optlen = sizeof(napi_id);
if (getsockopt(client_fd, SOL_SOCKET, SO_INCOMING_NAPI_ID, &napi_id, &optlen) == 0) {
/* تعيين client_fd لخيط العمل المخصص لمعالجة حلقة NAPI المحددة */
assign_socket_to_napi_worker(client_fd, napi_id);
}
يتيح الاستعلام عن NAPI ID لبوابات الحافة إنشاء ربط مباشر بنسبة 1:1 بين صفوف العتاد وحلقات معالجة الأحداث البرمجية. فإذا كانت بطاقة شبكة بسرعة 100 غيغابت توفر 32 صف استقبال، ينشئ الخادم 32 خيط عمل، يختص كل منها بصف عتادي مخصص.
2. مسارات حزم متماثلة ثنائية الاتجاه مع تكامل تقنية XPS
تتطلب الإنتاجية القصوى تماثلاً دقيقًا في مساري الاستقبال (RX) والإرسال (TX). فعندما تتلقى البوابة حزمة مشفرة، وتفك تشفيرها، ثم ترسلها عبر واجهة داخلية، فإن استخدام نواة غير منسقة للإرسال يؤدي إلى تنافس وتصادم على أقفال صفوف الإرسال TX.
مسار عتادي متماثل شامل:
[ صف RX لبطاقة الشبكة 2 ] ──► [ Softirq النواة 2 ] ──► [ خيط العمل 2 (SO_INCOMING_CPU) ]
│
▼ (المعالجة وفك التشفير)
[ صف TX لبطاقة الشبكة 2 ] ◄── [ صف XPS النواة 2 ] ◄── [ ذاكرة الإرسال المؤقتة (بلا تصادم) ]
وبدمج SO_INCOMING_NAPI_ID في الاستقبال مع تقنية توجيه حزم الإرسال (XPS) المخصصة لنفس النواة، تتحرك الحزمة في مسار مغلق من السلك إلى مساحة المستخدم ثم تعود للسلك دون أي عبور لحدود الأنوية المعالجة.
البنية التنفيذية: حلقات العمل المثبتة وتقسيم المقابس
يتطلب تطبيق SO_INCOMING_CPU في بوابات الحافة الموجهة بالأحداث إعدادًا منظمًا:
- استكشاف الهيكلية: قراءة المسارات في
/sys/devices/system/cpu/لتحديد توزيع الأنوية الفيزيائية ومجموعات NUMA. - إنشاء مصفوفة مقابس الاستماع: إنشاء مجموعة مقابس مهيأة بواسطة
SO_REUSEPORTوSO_INCOMING_CPUمرتبطة بكل نواة معالج. - تثبيت خيوط العمل: استدعاء
pthread_setaffinity_np()عند بدء كل خيط لتخصيص تنفيذه على النواة المحددة فقط. - التسجيل في Epoll: يسجل كل خيط عمل مقبس الاستماع الخاص به والاتصالات المنسوبة إليه ضمن مثيل
epoll_create1()المحلي.
/* نقطة انطلاق خيط العمل */
void *worker_loop(void *arg) {
int core_id = (int)(intptr_t)arg;
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(core_id, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
/* حلقة epoll مستقلة للنواة */
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listeners[core_id], &ev);
while (running) {
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for (int i = 0; i < nfds; i++) {
/* معالجة فائقة السرعة ضمن ذاكرة L1/L2 المحلية */
handle_active_socket(events[i].data.fd);
}
}
return NULL;
}
تحليل اختبارات الأداء المصغرة: استقرار زمن الاستجابة ومكاسب الإنتاجية
أظهرت القياسات المعيارية على خوادم AMD EPYC تضم 128 نواة تحت تدفق بيانات مستمر بمعدل 20 غيغابت في الثانية تحسنًا جوهريًا عند تفعيل تقارب المقابس للأنوية:
مقارنة مؤشرات الأداء تحت الأحمال المتعددة الأنوية:
مؤشر القياس Epoll القياسي SO_INCOMING_CPU + تثبيت NAPI
────────────────────────────────────────────────────────────────────────────────
الإنتاجية (Mpps) 3.4 Mpps 5.8 Mpps (+70%)
إخفاقات ذاكرة L3 المؤقتة 28.4% 6.2% (-78%)
المقاطعات البينية (IPI) 142,000 / ثانية 1,800 / ثانية (-98%)
زمن استجابة الذيل (p99) 14.2 ms 1.1 ms (-92%)
أعباء النظام (IPI/Lock) 34% 7%
بفضل التخلص من إبطال الذاكرة المؤقتة التبادلي، يقل الضغط على ناقل الذاكرة بشكل لافت، مما يمكن الخادم من استيعاب الارتفاعات الفجائية في حركة المرور دون أي فقدان للحزم داخل بطاقة الشبكة.
مسار بيانات RiceGuard: عزل الأنوية للقياس عن بعد وإعادة التوجيه بسرعة السلك
تدمج VPNRice تقنيتي SO_INCOMING_CPU و SO_INCOMING_NAPI_ID مباشرة في مسار بيانات مرحلات RiceGuard. وأثناء خدمة الأنفاق المؤسسية الضخمة والعقد الموزعة، يمثل الحفاظ على سرعة معالجة متسقة للحزم ركيزة أساسية لأمن الشبكة.
وفي مرحلات RiceGuard، تخضع الحزم الواردة لفحص سريع عبر XDP قبل تسليمها لوحدات التوجيه المثبتة ضمن بيئات NUMA في مساحة المستخدم. ويؤدي التوافق بين صفوف العتاد وخيوط العمل إلى إلغاء أقفال المزامنة وأعباء تبديل السياق. يضمن هذا العزل المعماري ألا تؤثر طفرات طلبات الاتصال المفاجئة على الأنفاق النشطة، مما يجمع بين الأمان الراسخ وزمن الانتقال المنخفض وحماية الخصوصية الرقمية.



