تتعامل بوابات الخروج وخوادم التوجيه المتقدمة لشبكات VPN مع أحجام بيانات مجمعة تفوق 40 جيجابت في الثانية على الخوادم الحديثة متعددة الأنوية. وفي النمط البرمجي المعتاد لمقابس نظام لينكس، تفرض كل عملية إرسال بيانات عبر send(2) أو sendmsg(2) على النواة نسخ بايتات البيانات من ذاكرة مساحة المستخدم إلى وسائط التخزين المؤقت لحزم الشبكة (sk_buff).

ورغم أن هذا النسخ لا يمثل مشكلة في التعامل مع البيانات المحدودة، فإنه يتحول في البيئات كثيفة الاستخدام إلى عائق هيكلي، إذ تستهلك أنوية المعالج ما يصل إلى 35% من وقت التشغيل في تنفيذ أوامر نقل الذاكرة (copy_user_generic_string)، مما يؤدي إلى تشبع ناقل الذاكرة العشوائية وإزاحة البيانات المشفرة النشطة من ذاكرتي التخزين المؤقت السريعتين L1 و L2.

يقدم نواة لينكس حلا جذريا لهذه الإشكالية من خلال خيار الإرسال MSG_ZEROCOPY، والذي يتيح تثبيت صفحات الذاكرة الفيزيائية التابعة للتطبيق مباشرة في جداول صفحات النواة وربطها بقوائم التجميع والإرسال المباشر إلى بطاقة الشبكة عبر الوصول المباشر للذاكرة (DMA)، مما يلغي كليا الحاجة لنسخ البيانات في مسار الإرسال.

أعباء اختناق ناقل الذاكرة في مسارات الإرسال التقليدية

لتوضيح التأثير السلبي لنسخ الذاكرة في بوابات VPN فائقة السرعة، نستعرض مسار تدفق البيانات المشفرة:

[ تطبيق شبكة VPN في مساحة المستخدم ]
       | (البيانات محفوظة في ذاكرة التطبيق)
       | sendto(fd, buf, len, 0, ...)
       v
[ طبقة المقابس بالنواة: استدعاء __copy_from_user() ]
       | (نسخ البيانات بالكامل إلى صفحات sk_buff)
       v
[ حزمة بروتوكولات النواة: التوجيه والجدار الناري ]
       |
       v
[ طابور برنامج تشغيل بطاقة الشبكة: تعيين DMA ]
       | (بطاقة الشبكة تقرأ البيانات مباشرة عبر PCIe DMA)
       v
[ بطاقة واجهة الشبكة الفعلية (NIC) ]

عند إرسال كتلة بيانات بحجم 16 كيلوبايت، يضطر المعالج لقراءة البايتات من الذاكرة الافتراضية للتطبيق ونقلها عبر مسجلات المعالج لكتابتها في صفحات جديدة بالنواة. وعند الوصول إلى معدل نقل 40 جيجابت، تتنقل أكثر من 5 جيجابايت من البيانات الخام في كل ثانية عبر ناقل الذاكرة الرئيسي.

ينتج عن هذه المعالجة ثلاثة تحديات رئيسية:

  1. مزاحمة النطاق الترددي للذاكرة: تحتاج خوارزميات التشفير مثل ChaCha20-Poly1305 إلى سعة نقل مرتفعة للذاكرة، وتؤدي عمليات النسخ المتكررة إلى استنزاف القنوات المتاحة.
  2. استنزاف الذاكرة المخبأة للمعالج: تتسبب القراءة والكتابة المستمرة للبيانات المؤقتة في إخراج جداول التوجيه وسياقات الجلسات من ذاكرة المعالج السريعة.
  3. زيادة تأخير تبديل السياق: تجبر عمليات النسخ المتزامنة مسار التنفيذ على البقاء محجوزا داخل النواة حتى اكتمال نسخ كامل البيانات.

المعمارية التقنية لآلية MSG_ZEROCOPY

تحيل تقنية MSG_ZEROCOPY مهمة قراءة الذاكرة مباشرة إلى المكونات العتادية لبطاقة الشبكة:

[ تطبيق VPN: مخزن البيانات buf ]
       |
       | 1. send(fd, buf, len, MSG_ZEROCOPY)
       |    (النواة تثبت الصفحات عبر get_user_pages_fast)
       |
       +======================+ (مرجع مباشر دون نسخ أي بايت)
       |                      |
       v                      v
[ بنية skb: مصفوفة frags[] ] -> [ عتاد بطاقة الشبكة: قراءة DMA مباشرة من RAM ]
       |
       | 2. إرسال الحزمة عبر كابل الشبكة
       | 3. البطاقة تصدر إشعار مقاطعة اكتمال DMA
       v
[ طابور أخطاء المقبس: SO_EE_ORIGIN_ZEROCOPY ]
       |
       | 4. التطبيق يسترجع buf عبر recvmsg(MSG_ERRQUEUE)
       v
[ إعادة استخدام المخزن لدورة تشفير جديدة ]

1. تثبيت الصفحات بواسطة get_user_pages_fast()

عند تفعيل علامة MSG_ZEROCOPY، تتفادى النواة تخصيص مساحات لحمولة الحزمة، وتستدعي بدلا من ذلك دالة get_user_pages_fast() لتثبيت صفحات الذاكرة الحقيقية التي تشير إليها مؤشرات التطبيق، مع تسجيل مراجع الصفحات ضمن بيانات المقبس المشتركة (skb_shinfo(skb)->frags[]).

وبذلك يتولى المعالج معالجة ترويسات الحزم فقط (UDP و IP)، بينما تقرأ بطاقة الشبكة حمولة البيانات مباشرة من ذاكرة المستخدم عبر مسار PCIe DMA السريع.

2. طابور أخطاء المقبس وإشعارات الإنجاز غير المتزامنة

نظرا لأن بطاقة الشبكة تقرأ البيانات بشكل غير متزامن، لا يجوز للتطبيق الكتابة في المخزن المؤقت أو تحريره فور انتهاء أمر الإرسال تجنبا لتلف البيانات أثناء البث. وتتولى النواة إشعار التطبيق بإمكانية التحرير عبر طابور أخطاء المقبس (MSG_ERRQUEUE):

#define _GNU_SOURCE
#include <sys/socket.h>
#include <linux/errqueue.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>

/* تفعيل نمط النقل دون نسخ على المقبس */
int enable_zerocopy(int sock_fd) {
    int one = 1;
    if (setsockopt(sock_fd, SOL_SOCKET, SO_ZEROCOPY, &one, sizeof(one)) < 0) {
        perror("فشل تفعيل setsockopt SO_ZEROCOPY");
        return -1;
    }
    return 0;
}

/* استرجاع المخازن المؤقتة بعد اكتمال نقل DMA الفعلي */
void reap_zerocopy_completions(int sock_fd) {
    char control[1024];
    struct msghdr msg = {0};
    struct cmsghdr *cmsg;
    struct sock_extended_err *serr;

    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);

    /* استلام الإشعارات المكتملة من طابور المقبس */
    while (recvmsg(sock_fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT) > 0) {
        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
            if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) {
                serr = (struct sock_extended_err *)CMSG_DATA(cmsg);
                if (serr->ee_origin == SO_EE_ORIGIN_ZEROCOPY) {
                    uint32_t lo = serr->ee_info;  /* الرقم التسلسلي الأول المكتمل */
                    uint32_t hi = serr->ee_data;  /* الرقم التسلسلي الأخير المكتمل */
                    /* المخازن في النطاق [lo, hi] أصبحت جاهزة لإعادة الاستخدام */
                }
            }
        }
    }
}

تعين النواة رقما تسلسليا متزايدا لكل عملية إرسال، وفور إتمام بطاقة الشبكة لمعالجة البيانات وتحرير الحزمة، يرسل النظام إشعارا يتضمن أرقام الحزم المنتهية (ee_info إلى ee_data).

معايير الجدوى التقنية للنقل دون نسخ

لا تحقق تقنية النقل دون نسخ مكاسب ملموسة مع كافة أحجام الحزم؛ إذ يتطلب تثبيت صفحات الذاكرة ومعالجة إشعارات الإنجاز كلفة حسابية محددة. وتتوزع المعايير الهندسية تبعا لحجم البيانات:

حجم حمولة الحزمة الإرسال التقليدي sendmsg() الإرسال عبر MSG_ZEROCOPY التوصية الهندسية
صغيرة (أقل من 1500 بايت) معالجة سريعة عبر الكاش عبء تثبيت الصفحات يتجاوز الفائدة استخدام النمط المعتاد
متوسطة (1500 – 4000 بايت) أداء متقارب أداء متقارب حيادي
كبيرة (4000 – 16000 بايت) ارتفاع الضغط على ناقل الذاكرة انخفاض بنسبة 30% في جهد المعالج يوصى بالاعتماد
فائقة الحجم (أكبر من 16 ك.ب) اختناق شديد في موارد المعالج انخفاض بنسبة 55% في جهد المعالج ترقية حاسمة للأداء

وفي بوابات شبكات VPN التي تعتمد على تقنية تجميع الحزم (UDP GSO) لدمج الحزم في كتل بيانات كبيرة (من 16 إلى 64 كيلوبايت)، تحقق تقنية MSG_ZEROCOPY طفرة نوعية في معدلات التدفق.

التطبيق الميداني في منظومة VPNRice وبروتوكول RiceGuard

وظفت شبكة VPNRice تقنية MSG_ZEROCOPY في بوابات Anycast الموزعة لتسريع مسارات إرسال بروتوكول RiceGuard:

[ محرك التشفير RiceGuard ]
       | (تشفير الحزم المجمعة في مخزن حلقي ثابت بحجم 64 كيلوبايت)
       |
       | 1. sendto(udp_fd, ring_slot, 64KB, MSG_ZEROCOPY, ...)
       v
[ طبقة المقابس في نواة لينكس ]
       | (تثبيت صفحات الذاكرة دون نسخ البيانات)
       v
[ بطاقة الشبكة 100GbE من إنتل أو ميلانوكس ]
       | (نقل الحزم مباشرة عبر PCIe DMA)
       |
       | 2. إشعار Error Queue يعيد الخانة إلى حوض المخازن
       v
[ استعادة المخزن لدورات الإرسال اللاحقة ]
  1. المخازن الحلقية الثابتة: تخصص RiceGuard مسبقا مساحات ذاكرة مثبتة، وتكتب البيانات المشفرة فيها مباشرة لتجنب التخصيص المتكرر أثناء العمل.
  2. التكامل بين UDP GSO والنقل دون نسخ: تجمع RiceGuard ما يصل إلى 45 حزمة في كتلة واحدة بحجم 64 كيلوبايت وترسلها بأمر واحد دون تكرار للبيانات في الذاكرة.
  3. توفير مسارات الذاكرة لعمليات التشفير: يتيح إسناد مهام النقل إلى بطاقة الشبكة توفير كامل سعة ناقل الذاكرة لحسابات ChaCha20-Poly1305، مما يساعد خوادم الخدمة على استيعاب سرعات تصل إلى 40 جيجابت دون أي فقد في الحزم.

أوامر التدقيق والتشخيص في بيئة لينكس

يستطيع مسؤولو الأنظمة التحقق من عمل مقابس النقل دون نسخ ومراقبة مؤشرات الطوابير عبر الأدوات القياسية:

# مراجعة إحصائيات المقابس وحزم الشبكة
cat /proc/net/snmp | grep -E "Udp:|Ip:"

# فحص عمق طوابير المقابس وإشعارات الأخطاء
ss -u -m -i

يتيح التخلي عن أعباء نسخ الذاكرة لبوابات VPN الحديثة الاستفادة القصوى من سرعة العتاد الشبكي مع المحافظة على كفاءة أنوية المعالج.