28. Multi-process Sample Application

28. Multi-process Sample Application

        本章描述了在DPDK中多进程示例应用程序。


28.1. Example Applications

28.1.1. Building the Sample Applications

        多进程示例应用程序的构建方式与其他示例应用程序相同,在“DPDK Getting Started Guide”中有文档记录。

        要编译示例应用程序,请参阅” Compiling the Sample Applications.“。

        应用程序位于multi_process子目录中。

!Note

        如果需要构建一个特定的多进程应用程序,最终的make命令可以只在该应用程序的目录中运行,而不需要在多进程目录最上层目录运行

(前提是环境库已经编译好,环境变量设置好了)


28.1.2. Basic Multi-process Example

        DPDK版本中的示例/ simple_mp文件夹中包含一个基本的示例应用程序,用来演示两个DPDK进程如何使用队列和内存池来共享信息。


28.1.2.1. Running the Application

        要运行应用程序,在一个终端中启动一个simple_mp进程,在coremask / corelist参数中至少设置两个核,如下:

  ./build/simple_mp -l 0-1 -n 4 --proc-type=primary
         对于第一个DPDK进程,proc-type参数可以忽略或设置为”auto“,因为所有DPDK进程将默认一个主实例,这意味着它们可以控制hugepage共享内存区域。该进程应该成功启动,并显示一个命令提示符如下:

$ ./build/simple_mp -l 0-1 -n 4 --proc-type=primary
EAL: coremask set to 3
EAL: Detected lcore 0 on socket 0
EAL: Detected lcore 1 on socket 0
EAL: Detected lcore 2 on socket 0
EAL: Detected lcore 3 on socket 0
...

EAL: Requesting 2 pages of size 1073741824
EAL: Requesting 768 pages of size 2097152
EAL: Ask a virtual area of 0x40000000 bytes
EAL: Virtual area found at 0x7ff200000000 (size = 0x40000000)
...

EAL: check igb_uio module
EAL: check module finished
EAL: Master core 0 is ready (tid=54e41820)
EAL: Core 1 is ready (tid=53b32700)

Starting core 1

simple_mp >
        为了运行同一二进制文件的副进程和主进程通信,coremask/corelist参数至少得设置两个核:

./build/simple_mp -l 2-3 -n 4 --proc-type=secondary
        在运行如上所示的副进程时,proc-type参数也可以被指定为auto。但是,省略该参数将导致该进程启动时尝试作为一个主进程而不是副过程。

(上面的意思就是副进程启动时只能设置proc-type参数为secondary,auto,不能省略

        一旦正确地指定了进程类型,进程就会正常启动,显示和主进程初始化时相同的大量状态消息。同样,您将看到一个命令提示符。

        当两个进程都运行时,可以使用send命令在它们之间发送消息。在任何阶段,任何进程都可以使用quit命令退出。

EAL: Master core 10 is ready (tid=b5f89820)           EAL: Master core 8 is ready (tid=864a3820)
EAL: Core 11 is ready (tid=84ffe700)                  EAL: Core 9 is ready (tid=85995700)
Starting core 11                                      Starting core 9
simple_mp > send hello_secondary                      simple_mp > core 9: Received 'hello_secondary'
simple_mp > core 11: Received 'hello_primary'         simple_mp > send hello_primary
simple_mp > quit                                      simple_mp > quit
!Note

        如果主进程被终止,副进程也必须的终止,然后在主进程启动之后再启动。这是必要的,因为主进程在启动的时候将清除并重置共享内存区域,从而使副进程的指针失效。可以在不影响主进程的情况下停止和重启副进程。


28.1.2.2. How the Application Works

        这个示例应用程序的核心是在共享内存中使用两个队列和一个内存池。这三个对象是由主进程在启动时创建的,副进程不能在内存中创建对象,因为它不能保留内存区域,副进程在启动时使用查找函数关联到这些对象上。

if (rte_eal_process_type() == RTE_PROC_PRIMARY){
    send_ring = rte_ring_create(_PRI_2_SEC, ring_size, SOCKET0, flags);
    recv_ring = rte_ring_create(_SEC_2_PRI, ring_size, SOCKET0, flags);
    message_pool = rte_mempool_create(_MSG_POOL, pool_size, string_size, pool_cache, priv_data_sz, NULL, NULL, NULL, NULL, SOCKET0, flags);
} else {
    recv_ring = rte_ring_lookup(_PRI_2_SEC);
    send_ring = rte_ring_lookup(_SEC_2_PRI);
    message_pool = rte_mempool_lookup(_MSG_POOL);
}
        注意,在主进程中创建send_ring的参数名字_PRI_2_SEC,在副进程中是作为recv_ring的参数的。

(目的就是主进程发数据,副进程收数据,副进程发数据,主进程收数据)

        一旦这些ring和内存池在主进程和副进程中都可用,应用程序就会启动两个线程分别用于发送和接收消息。接收线程简单地将接收ring上的任何消息提出队列,打印它们,并将消息使用的缓冲区空间释放回内存池。发送线程利用用户在交互命令行中输入的内容生成消息准备发送。一旦用户发出了发送命令,就会从内存池中分配一个缓冲区,并填充消息内容,然后发送到适当的rte_ring上的队列。 

 

28.1.3. Symmetric Multi-process Example 

        DPDK多进程支持的第二个示例演示了一组进程如何并行运行,每个进程执行相同的包处理操作。(由于每个进程在功能上与其他进程是相同的,所以我们将其称为对称多处理,以区别于不对称的多处理——如下一个示例中所看到的客户机-服务器模式,在这里,不同的进程执行不同的任务,通过协作以形成一个包处理系统)下图显示了通过应用程序的数据流,使用了两个进程。

    根据上图所示, 每个进程接收指定接口的数据包。RSS用于将每个端口上的接收数据包分发到不同的硬件RX队列上。每个进程在每个端口上读取各自的指定RX队列,因此不必和其他进程竞争RX队列。类似地,每个进程在每个端口上有各自的TX队列以发送数据包。 


28.1.3.1. Running the Application

    与simple_mp示例一样,symmetric_mp程序的第一个实例必须以主进程的形式运行,像其他一些DPDK应用程序一样,EAL库也提供了一些特定的参数。这些额外的参数是:  

  • -p <portmask>,其中,portmask是一个十六进制位掩码,用于在系统上使用的端口。例如:-p 3 仅使用端口0和1。
  • –num-procs <N>,其中N是将会并行运行执行分组处理的symmetric_mp进程的总个数。该参数用于在每个网络端口上配置接收队列的适当数量。
  • –proc-id <n> 其中n为0 <= n < N(进程的数量,上面指定的进程个数)的数值。这将标识正在运行的symmetric_mp进程,以便每个进程可以在每个网络端口上读取唯一的接收队列。

        第二个symmetric_mp进程也必须指定的这些参数,前两个参数必须和主进程的参数相同,否则会返回错误。

        例如,要运行一组4个symmetric_mp实例,在核1 - 4上运行,在端口0和1之间执行二层数据包转发,可以使用以下命令(假设以root身份运行):     

# ./build/symmetric_mp -l 1 -n 4 --proc-type=auto -- -p 3 --num-procs=4 --proc-id=0
# ./build/symmetric_mp -l 2 -n 4 --proc-type=auto -- -p 3 --num-procs=4 --proc-id=1
# ./build/symmetric_mp -l 3 -n 4 --proc-type=auto -- -p 3 --num-procs=4 --proc-id=2
# ./build/symmetric_mp -l 4 -n 4 --proc-type=auto -- -p 3 --num-procs=4 --proc-id=3

!Note

        在上面的示例中,可以显式指定进程类型为主或副,而不用auto。当使用auto时,第一个进程运行时会创建所有进程所需的所有内存结构——不管它的proc-id是0、1、2或3。

!Note

        对于对称的多进程示例,因为所有进程都以相同的方式工作,一旦hugepage共享内存和网络端口被初始化,如果主进程挂掉,不必重新启动所有其它进程。相反,通过在命令行中显式地将proc-type设置为副进程,可以重新启动该进程。(所有后续启动的实例也都需要显式指定,因为自动检测将检测到没有主进程正在运行,因此尝试重新初始化共享内存。)


28.1.3.2. How the Application Works

        主副进程中的初始化工作大部分是相同的,调用rte_eal_init()、1g和10g网卡驱动程序初始化,然后调用rte_pci_probe()函数。此后,初始化的完成取决于该进程是否被配置为主进程或是副进程。

        在主进程中,为数据包mbufs创建了一个内存池,并初始化了要使用的网络端口——通过命令行上传递的num-procs参数来确定每个端口的RX和TX队列的数量。被初始化的网络端口的结构存储在共享内存中,副进程在初始化时可以访问。

if (num_ports & 1)
   rte_exit(EXIT_FAILURE, "Application must use an even number of ports\n");

for(i = 0; i < num_ports; i++){
    if(proc_type == RTE_PROC_PRIMARY)
        if (smp_port_init(ports[i], mp, (uint16_t)num_procs) < 0)
            rte_exit(EXIT_FAILURE, "Error initializing ports\n");
}
        在副进程中,不会初始化网络端口,而是使用主进程导出的端口信息,副进程通过端口信息访问每个网络端口的硬件和软件ring。类似地,通过内存池的名称进行查找,可以访问mbufs的内存池:

mp = (proc_type == RTE_PROC_SECONDARY) ? rte_mempool_lookup(_SMP_MBUF_POOL) : rte_mempool_create(_SMP_MBUF_POOL, NB_MBUFS, MBUF_SIZE, ... )

        一旦这个初始化完成,每个进程的主循环开始运行(主进程和副进程的主循环函数是完全相同的)——每个进程从每个端口的对应于它的proc - id参数的队列上读取数据,写入数据到对应的输出端口队列上。


28.1.4. Client-Server Multi-process Example

        第三个DPDK多进程应用示例中演示了如何使用客户机-服务器模型的多进程设计来进行数据包处理。在本例中,单个服务器进程接收来自网络端口的数据包,并通过轮询的方式将数据包分发到一组客户机进程中,它们执行实际的包处理。在这种情况下,客户端应用程序只是通过将每个包发送到不同的网络端口来执行二层转发。

        下图显示了通过应用程序的数据流,使用了两个客户机进程。


28.1.4.1. Running the Application

        服务器进程必须先运行以便初始化客户进程所使用的全部内存结构。除EAL参数外,具体应用参数为:

  • -p <portmask >,其中,portmask是一个十六进制位掩码,用于在系统上使用的端口。例如:-p 3 仅使用端口0和1。
  • -n <num-clients>,num-client参数是处理接收来自服务器的数据包的客户机进程的数量。
!Note
        在服务器进程中,一个线程-主线程,也就是coremask / corelist参数中编号最低的lcore,执行所有的包I / O。如果一个coremask / corelist被指定为一个以上的lcore集(多个core),那么将会使用一个额外的lcore来定期打印数据包统计信息。
        由于服务器应用程序在共享内存中存储配置数据,包括要使用的网络端口,客户机实例ID-客户机进程的唯一应用程序参数。因此,要在lcore 1上运行一个服务器应用程序(使用lcore 2打印统计数据)以及在lcore 3和4上运行的两个客户机进程,可以使用以下命令:
# ./mp_server/build/mp_server -l 1-2 -n 4 -- -p 3 -n 2
# ./mp_client/build/mp_client -l 3 -n 4 --proc-type=auto -- -n 0
# ./mp_client/build/mp_client -l 4 -n 4 --proc-type=auto -- -n 1
!Note
        如果服务器应用程序挂掉并且需要重新启动,那么所有客户机应用程序都需要重新启动,因为服务器应用程序不支持作为副进程运行。任何需要重新启动的客户机进程都可以在不影响服务器进程的情况下重新启动。

28.1.4.2. How the Application Works
        服务器进程执行网络端口和数据结构初始化时,就像在对称多进程中作为主进程运行时一样。此示例应用程序的另一个增强是,服务器进程将其端口配置数据存储在hugepage共享内存中的内存区域中。这消除了客户端进程像对称多进程应用程序在命令行上传递端口掩码参数的需要,因此消除了不匹配的参数作为潜在的错误来源。
        与服务器进程被设计为仅作为一个主进程实例来运行的方式相同,客户端进程被设计为仅作为副进程实例运行。它们没有试图创建共享内存对象的代码。相反,通过调用rte_ring_lookup()和rte_mempool_lookup(),可以获得所有需要的ring和内存池。通过加载网络端口驱动程序和探测PCI总线来获得进程使用的网络端口,这将像在对称多进程示例中一样,使用由主/服务器进程配置的设置信息自动访问网络端口。
        一旦初始化了所有应用程序,服务器就会依次从每个网络端口读取数据包,按照轮询的方式将这些包分发到客户端队列(每个客户端进程的软件ring)。在客户端,数据包被尽可能快的从ring上读取,然后被路由到一个不同的网络端口。使用的路由非常简单。第一个NIC端口上接收的所有数据包都在第二个端口上传输,反之亦然。类似地,在第3和第4个网络端口之间路由数据包,等等。发送数据包是通过将数据包直接写入到网络端口来完成的;它们不会返回给服务器进程。
       在服务器和客户端进程中,传出的包在被发送之前都会被缓存,从而允许在一次突发事件中发送多个包以提高效率。例如,客户端进程将发送数据包到缓存,直到缓冲区满,或者直到没有来自服务器的其他数据包为止。

28.1.5. Master-slave Multi-process Example
        DPDK多进程的第四个示例演示了一个主从模型,该模型提供了在副进程崩溃或遇到意外情况时应用程序恢复的能力。此外,它还演示了浮动处理,它可以在不同的核上运行,与传统的将进程/线程绑定到特定CPU核,使用mempool结构的本地缓存机制的方式不同。
        该示例应用与L2转发示例应用程序有相同的功能,因此本章内容不包括l2转发功能,而只描述了仅在这个多进程示例中引入的功能。l2转发相关信息请参考 L2 Forwarding Sample Application (in Real and Virtualized Environments)
        与前面所有进程都是从命令行输入参数启动的示例不同,在本例中,只有一个进程是从命令行启动,该启动进程创建其他进程。下一节将对此进行更详细的描述。

28.1.5.1. Master-slave Process Models
        在本文档中, 从命令行启动的进程称主进程。由主进程创建的进程称为从属进程。应用程序只有一个主进程,但是可以有多个从属进程。
        一旦主进程开始运行,它将尝试初始化所有资源,如内存、CPU内核、驱动程序、端口等等,就像其他示例主程序一样。此后,它将创建从属进程,如下图所示。

        主进程调用rte_eal_mp_remote_launch() EAL函数通过pipe为每个“ pinned thread”启动应用函数。然后,它等待检查是否有任何从属进程退出。如果退出,主进程程将尝试重新初始化属于该从属进程的资源,并再次在“ pinned thread”条目中启动它们。下一节将更详细地描述恢复过程。
 (pinned thread-固定线程,把线程绑定到固定cpu核上运行
        当从pipe中读取任何数据后,EAL的“ pinned thread”就会尝试调用应用程序指定的函数。在主进程指定处理函数中,会调用fork()创建一个执行L2转发任务的从属进程。然后,函数将等待从属进程退出,被杀死或崩溃。此后,它通知该事件的监听方并返回。最后,在新函数启动之前,“ pinned thread”将一直睡眠。
(个人理解是主进程在启动后初始化好资源,然后创建了固定个数的线程,主进程(也就是主线程)多次调用rte_eal_mp_remote_launch()启动每个线程(指定函数),线程函数就是在创建线程时传入的函数,这个函数会创建一个从属线程来处理数据,而这个线程就监视自己创建的从属进程的情况,在从属进程退出或是发生异常后上报给主进程 )
        在讨论了主从模型之后,还需要提到另一个问题,全局变量和静态变量。
        对于多线程,所有全局变量和静态变量只有一个副本,如果可以,任何线程都可以访问它们。因此,它们可以用于在线程间同步或共享数据。
        在前面的示例中,每个进程在内存中都有独立的全局变量和静态变量,并且相互独立。如果需要共享信息,应该部署一些通信机制,如memzone、ring、共享内存等等。全局变量或静态变量不是在进程间共享数据的有效方法。对于本例中的变量,从一个方面来说,子进程在主进程创建后继承了所有主进程的变量。另一方面,其他进程无法知道自己被创建后这些变量是否被一个或多个进程修改,因为这是一个多进程地址空间的性质。但这并不意味着这些变量不能用于共享或同步数据;这取决于用例。以下是可能的用例:
  1. 主进程启动并初始化一个变量,在创建了从属进程之后,它将不会被更改。这种情况下就可以了。 
  2. 在创建了从属进程之后,主进程或从进程核需要更改一个变量,但是其他进程不需要知道更改。这种情况也可以。
  3. 在创建了从属进程之后,主进程或从属进程需要改变一个变量。与此同时,其他一个或多个进程需要知晓改变。在这种情况下,不能使用全局变量和静态变量来共享信息。需要另一种沟通机制。一个没有锁保护的简单方法可以是由rte_malloc或memzone分配的堆缓冲区提供。
28.1.5.2. Slave Process Recovery Mechanism
        在讨论恢复机制之前,有必要知道在先前的一个从属实例已经退出,一个新的从属实例可以运行之前需要什么。  
        当一个从属进程退出时,系统将自动收回分配给该进程的所有资源。但是,这并不包括DPDK分配的资源。所有的硬件资源都在所有的进程中共享,其中包括memzone、mempool、ring、rte_malloc库分配的堆缓冲区等等。如果新实例运行前并没有返回已分配的资源,则资源分配失败或硬件资源永远丢失。  
        当一个从属进程运行时,它可能依赖于其他进程。他们可能顺序执行指令;他们可能通过ring进行交流;他们可能共享同一个端口来接收和转发数据;他们可能用锁结构来独占某些临界资源。如果队友进程离开了,依赖于队友进程的进程会发生什么?结果会多种多样,因为依赖关系是复杂的。这取决于进程间共享了什么。但是,如果一个从属进程退出了,就必须通知队友。然后,队友将意识到这一点,并等待新实例开始运行。
        因此,为了提供恢复新从属实例的能力,如果前一个实例退出,则需要提供几个机制:
  1. 为每个从属进程保留一个资源列表。在一个从属进程运行之前,主进程应该准备一个资源列表。在退出后,主进程可以删除已分配的资源并创建新的资源,或者重新初始化新实例所使用的资源。
  2. 为从属进程退出建立通知机制。在特定的从属进程退出后,应该通知主进程,然后帮助创建一个新的从属进程。该机制是在 Master-slave Process Models提供的。
  3. 在相关进程之间使用同步机制。主进程应该有能力停止或杀死从属进程,这个从属进程依赖于另一个进程,但另一个进程已经退出了。然后,在退出的从属进程的新实例开始运行之后,依赖项可以从一开始就恢复或生成。该示例将一个STOP命令发送到依赖于退出进程的从属进程,然后它们将退出。此后,主进程为已退出的从属进程创建新实例。
        下图描述了从属进程的恢复:


28.1.5.3. Floating Process Support
        当DPDK应用程序运行时,总是会传递一个- c选项,以指示启用了哪些cpu核。然后,DPDK为每个启用的cpu核创建一个线程。通过这样做,它在已启用的cpu核和每个线程之间创建了一个1:1的映射。启用的cpu核总是有一个ID,因此,每个线程在DPDK执行环境中都有一个惟一的cpu核ID。通过ID,每个线程都可以很容易地访问它的结构或资源,而无需使用函数参数传递。它可以很容易地使用rte_lcore_id()函数来获得所调用的每个函数的值。
        对于没有以固定在一个核心上这种方式创建的线程/进程,它们不会拥有唯一的ID,而rte_lcore_id()函数也不会以正确的方式工作。然而,有时这些线程/进程仍然需要唯一的ID机制来轻松访问结构或资源。例如,DPDK mempool库为元素快速分配和释放提供了本地缓存机制(请参阅 Local Cache)。如果使用一个非唯一ID或一个假的ID,当两个或多个线程/进程使用相同的核心ID尝试使用本地缓存时,就会形成一个竞争环境。
        因此,使用- c选项传递参数的未使用的cpu核ID将用于组织cpu核ID分配数组。一旦生成了浮动进程,它将尝试从分配数组中分配一个惟一的cpu核ID,并在浮动进程退出时释放它。
(浮动线程 /进程和固定线程/进程刚好相反)
        生成浮动进程的一种方法是使用fork()函数,并从未使用的cpu核ID数组中分配惟一的核ID。但是,有必要编写新的代码以提供一个从属进程退出的通知机制,并确保进程恢复机制能够使用它。
        为了避免产生冗余代码,主从进程模型仍然被用于生成浮动进程,取消浮动进程和特定cpu核的关联。此外,清除根据带有核掩码形成的1:1映射关系的DPDK线程的核ID。此后,从未使用的cpu核ID分配数组获取一个新的核ID。

28.1.5.4. Run the Application
        这个示例有一个类似于L2转发示例应用程序的命令行,其中有一些区别。
        要运行应用程序,在终端中启动l2fwd_fork二进制文件副本。与L2转发示例不同的是,这个示例至少需要三个cpu核,因为主进程要负责从属进程的恢复。该命令如下:
#./build/l2fwd_fork -l 2-4 -n 4 -- -p 3 -f
        这个例子提供了另一个- f 选项来指定浮动进程的启动。如果没有指定,这个示例将使用固定核方式来执行L2转发任务。
        要验证恢复机制,请按照以下步骤进行:首先,检查从属进程的PID:
#ps -fe | grep l2fwd_fork
root 5136 4843 29 11:11 pts/1 00:00:05 ./build/l2fwd_fork
root 5145 5136 98 11:11 pts/1 00:00:11 ./build/l2fwd_fork
root 5146 5136 98 11:11 pts/1 00:00:11 ./build/l2fwd_fork
        然后,杀掉一个从属进程:
#kill -9 5145
        1或2秒后,检查从属是否已经恢复:
#ps -fe | grep l2fwd_fork
root 5136 4843 3 11:11 pts/1 00:00:06 ./build/l2fwd_fork
root 5247 5136 99 11:14 pts/1 00:00:01 ./build/l2fwd_fork
root 5248 5136 99 11:14 pts/1 00:00:01 ./build/l2fwd_fork
        还可以监视静态流量生成器,以查看是否恢复了从属进程。

28.1.5.5. Explanation
       正如前面所述,不是所有的全局变量和静态变量都需要在多个进程中进行更改;取决于它们如何使用。在这个例子中,从属进程需要更新数据包丢弃,转发,接收等统计静态变量,主进程需要将它们打印出来。因此,这需要使用rte_zmalloc分配堆缓冲区。此外,如果指定了- f选项,则需要一个数组来存储已分配的浮动进程的核ID,以便在一个从属进程在意外退出后,主进程可以回收这个核ID。
static int
l2fwd_malloc_shared_struct(void)
{
    port_statistics = rte_zmalloc("port_stat", sizeof(struct l2fwd_port_statistics) * RTE_MAX_ETHPORTS, 0);

    if (port_statistics == NULL)
        return -1;

    /* allocate mapping_id array */

    if (float_proc) {
        int i;

        mapping_id = rte_malloc("mapping_id", sizeof(unsigned) * RTE_MAX_LCORE, 0);
        if (mapping_id == NULL)
            return -1;

        for (i = 0 ;i < RTE_MAX_LCORE; i++)
            mapping_id[i] = INVALID_MAPPING_ID;

    }
    return 0;
}

        对于每个从属进程,从一个端口接收数据包,并将其转发到另一个被其它从属进程操作的端口上。如果另一个从属进程意外退出,它所操作的端口可能无法正常工作,因此第一个从属进程不能将数据包转发到这个端口。在这种情况下,不同从属进程间存在依赖端口的关系,所以,主进程应该认识到这种依赖关系。下面是检测这种依赖关系的代码:
for (portid = 0; portid < nb_ports; portid++) {
    /* skip ports that are not enabled */

    if ((l2fwd_enabled_port_mask & (1 << portid)) == 0)
        continue;

    /* Find pair ports' lcores */

    find_lcore = find_pair_lcore = 0;
    pair_port = l2fwd_dst_ports[portid];

    for (i = 0; i < RTE_MAX_LCORE; i++) {
        if (!rte_lcore_is_enabled(i))
            continue;

        for (j = 0; j < lcore_queue_conf[i].n_rx_port;j++) {
            if (lcore_queue_conf[i].rx_port_list[j] == portid) {
                lcore = i;
                find_lcore = 1;
                break;
            }

            if (lcore_queue_conf[i].rx_port_list[j] == pair_port) {
                pair_lcore = i;
                find_pair_lcore = 1;
                break;
            }
        }

        if (find_lcore && find_pair_lcore)
            break;
    }

    if (!find_lcore || !find_pair_lcore)
        rte_exit(EXIT_FAILURE, "Not find port=%d pair\\n", portid);

    printf("lcore %u and %u paired\\n", lcore, pair_lcore);

    lcore_resource[lcore].pair_id = pair_lcore;
    lcore_resource[pair_lcore].pair_id = lcore;
}

        在启动从属进程之前,有必要设置主进程和从属进程之间的通信通道,以便主进程在该从属进程依赖的另一个从属进程退出时可以通知到它。此外,主进程需要注册一个回调函数,以便在指定的从属进程退出时调用它。
for (i = 0; i < RTE_MAX_LCORE; i++) {
    if (lcore_resource[i].enabled) {
        /* Create ring for master and slave communication */

        ret = create_ms_ring(i);
        if (ret != 0)
            rte_exit(EXIT_FAILURE, "Create ring for lcore=%u failed",i);

        if (flib_register_slave_exit_notify(i,slave_exit_cb) != 0)
            rte_exit(EXIT_FAILURE, "Register master_trace_slave_exit failed");
    }
}

        在启动从属进程之后,主进程周期性的打印端口统计信息。如果一个表明从属进程退出的事件被检测到,主进程将发送STOP命令到依赖于退出从属进程的另一个从属进程并等待它退出。然后,它试图清理执行环境并准备新的资源。最后,启动新的从属实例。
while (1) {
    sleep(1);
    cur_tsc = rte_rdtsc();
    diff_tsc = cur_tsc - prev_tsc;

    /* if timer is enabled */

    if (timer_period > 0) {
        /* advance the timer */
        timer_tsc += diff_tsc;

        /* if timer has reached its timeout */
        if (unlikely(timer_tsc >= (uint64_t) timer_period)) {
            print_stats();

            /* reset the timer */
            timer_tsc = 0;
        }
    }

    prev_tsc = cur_tsc;

    /* Check any slave need restart or recreate */

    rte_spinlock_lock(&res_lock);

    for (i = 0; i < RTE_MAX_LCORE; i++) {
        struct lcore_resource_struct *res = &lcore_resource[i];
        struct lcore_resource_struct *pair = &lcore_resource[res->pair_id];

        /* If find slave exited, try to reset pair */

        if (res->enabled && res->flags && pair->enabled) {
            if (!pair->flags) {
                master_sendcmd_with_ack(pair->lcore_id, CMD_STOP);
                rte_spinlock_unlock(&res_lock);
                sleep(1);
                rte_spinlock_lock(&res_lock);
                if (pair->flags)
                    continue;
            }

            if (reset_pair(res->lcore_id, pair->lcore_id) != 0)
                rte_exit(EXIT_FAILURE, "failed to reset slave");

            res->flags = 0;
            pair->flags = 0;
        }
    }
    rte_spinlock_unlock(&res_lock);
}


        当从属进程被派生并开始运行时,它将检查浮动进程选项是否被设置。如果设置了,它会清除与指定核的关联,并将惟一核ID设置为0。然后,尝试分配一个新的核ID。因为核ID改变了,主进程根据核ID分配的资源不能正常工作,所以必须得根据新的核ID进行从新映射。
static int
l2fwd_launch_one_lcore( attribute ((unused)) void *dummy)
{
    unsigned lcore_id = rte_lcore_id();

    if (float_proc) {
        unsigned flcore_id;

        /* Change it to floating process, also change it's lcore_id */

        clear_cpu_affinity();

        RTE_PER_LCORE(_lcore_id) = 0;

        /* Get a lcore_id */

        if (flib_assign_lcore_id() < 0 ) {
            printf("flib_assign_lcore_id failed\n");
            return -1;
        }

        flcore_id = rte_lcore_id();

        /* Set mapping id, so master can return it after slave exited */

        mapping_id[lcore_id] = flcore_id;
        printf("Org lcore_id = %u, cur lcore_id = %u\n",lcore_id, flcore_id);
        remapping_slave_resource(lcore_id, flcore_id);
    }

    l2fwd_main_loop();

    /* return lcore_id before return */
    if (float_proc) {
        flib_free_lcore_id(rte_lcore_id());
        mapping_id[lcore_id] = INVALID_MAPPING_ID;
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/shaoyunzhe/article/details/78849962