x**3な人生

基本的にはメモ

git tagをリモートリポジトリに反映させた時のメモ

(追記)自分の用途だとannotateを付けないといけない事が分かったので修正。

ローカルで付けたタグをリモートリポジトリに反映させる方法。絶対に忘れる自信があるのでメモ。

自分の用途ではannotateを付ける必要があるので-aオプション、さらにこのオプションはメッセージを書くための-mオプションが必要なので、結果的に-amを付けないといけない。

前提として、リモートリポジトリはoriginとして登録されている。またタグを付けるのは最新のコミット。

$ git tag -am MESSAGE TAG
$ git push origin TAG

以上。

DPDKのPort Hotplug Frameworkを調べる

はじめに

ここの意訳。

45. Port Hotplug Framework — Data Plane Development Kit 18.11.0-rc0 documentation

内容

Port Hotplugフレームワークは実行中のDPDKアプリケーションにおいてポートのアタッチ、 デタッチを可能とする。 このPort HotplugはPMDの実装に依存するため、PMDによりサポートされていないものもある。 デタッチされたポートの削除は行わない。 また物理NICのポートはカーネルによるPCI Hotplugのサポートが必要である。

概要

Port Hotplugフレームワークの要件:

  • Port Hotplugを扱うDPDKアプリケーションは自身のポートを管理しなければならない。 Port HotplugフレームワークはDPDKアプリケーションからポートを管理できるように実装されている。 例えばDPDKアプリケーションがアタッチの関数を呼び出すと、アタッチされたポートのナンバーが返される。 またデタッチするにはそのナンバーを指定する。

  • 物理デバイスカーネルによるサポートが必要である。 物理デバイスのポートをアタッチするために、まずカーネルのユーザー空間I/Oドライバが そのデバイスを認識する。 そしてDPDKアプリケーションからポートをアタッチするためにPort Hotplugの関数を呼び出すことができる。

  • ポートをデタッチする前にstopとcloseを行う必要がある。 このためにDPDKアプリケーションはデタッチを行う前にrte_eth_dev_stop()rte_eth_dev_close()を 呼び出す必要がある。 これらの関数はPMDの終了化処理を行う。

  • Port HotplugフレームワークはレガシーなDPDKアプリケーションの振る舞いには影響しない。 もしPort Hotplugの関数を呼び出さなければ、レガシーなDPDKアプリケーションはそのまま動作する。

Port Hotplug APIの概要

アタッチ

rte_eth_dev_attach()はDPDKアプリケーションにポートをアタッチし、 アタッチされたポートのナンバーを返す。 なおデバイスはユーザー空間I/Oドライバに認識される必要がある。 この関数は0000:01:00.0の様なPCIアドレスもしくはnet_pcap0,iface=eth0の様な仮想デバイス名を受け取る。 この仮想デバイス名は-vdevオプションで与えるものと同じである。

デタッチ

rte_eth_dev_detach()はDPDKアプリケーションからデバイスをデタッチする。 返り値はそのデバイスPCIアドレスもしくは仮想デバイス名となる。

参考

testpmdにおいてPort Hotplugフレームワークが使用されている。

制限事項

  • Port HotplugのAPIはスレッドセーフで無い。
  • Linuxのみサポートし、BSDではサポートされない。
  • デタッチをサポートしないPMDがある。デタッチのためにはバスにおいてhot-unplugがサポートされている必要がある。もしそうで無い場合、rte_eth_dev_detach()ENOTSUPを返す。

以上

OpenStack Zunでコンテナを動かすメモ

基本的にはDeveloper Quick-Startを参照すればコンテナを起動することができます。 今回はcirros以外のイメージを登録して動かしてみたかったので、実際にやってみたメモ。

準備

DevStackで環境構築をしています。 DevStack自体の構築はQuick Startの通りです。

DevStackのインストール

まずsudo権限を持つユーザーstackを作成します。

$ sudo useradd -s /bin/bash -d /opt/stack -m stack
$ echo "stack ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/stack

次にstackになりDevStackのインストールを行います。

$ sudo su - stack
$ git clone https://git.openstack.org/openstack-dev/devstack
$ cd devstack

local.confdevstack/samplesにあるものを使用します。

$ cp samples/local.conf .

自分の環境に合わせてlocal.confを編集します。

Zunの設定

local.confにzunのための設定を追加します。kuryr-libnetworkはdockerにおいてOpenStackのネットワークを利用するためのプラグインであり、 Zunからネットワーク関連の設定を行うために必要となります。

enable_plugin zun https://git.openstack.org/openstack/zun
enable_plugin zun-tempest-plugin https://git.openstack.org/openstack/zun-tempest-plugin

#This below plugin enables installation of container engine on Devstack.
#The default container engine is Docker
enable_plugin devstack-plugin-container https://git.openstack.org/openstack/devstack-plugin-container

# In Kuryr, KURYR_CAPABILITY_SCOPE is `local` by default,
# but we must change it to `global` in the multinode scenario.
KURYR_CAPABILITY_SCOPE=global
KURYR_ETCD_PORT=2379
KURYR_PROCESS_EXTERNAL_CONNECTIVITY=False
enable_plugin kuryr-libnetwork https://git.openstack.org/openstack/kuryr-libnetwork

# install python-zunclient from git
LIBS_FROM_GIT="python-zunclient"

# Optional:  uncomment to enable the Zun UI plugin in Horizon
enable_plugin zun-ui https://git.openstack.org/openstack/zun-ui

stack.shの実行

これでZunをインストールする準備ができました。次の様にしてKeystoneやNova、Neutronなどともに Zunがインストールされます。

$ cd /opt/stack/devstack
$ ./stack.sh

stack.shが正常に終了すると、stackのホームディレクトリに各サービスのリポジトリが配置されていることが確認できます。

$ ls /opt/stack
horizon  python-zunclient  bin            cinder                     keystone
kuryr      tempest                 devstack  kuryr-libnetwork
...

コンテナイメージの作成と起動

Zunの設定が完了したので、次にコンテナを登録して起動できるようにします。 まずはじめにターミナルからOpenStackの操作ができるよう、adminとしてセットアップを行います。

$ source /opt/stack/devstack/openrc admin admin

コンテナイメージの作成

OpenStackに登録するコンテナを確認します。

$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
scratch              latest              23419253ba9b        3 days ago          0 B
<none>               <none>              a8a4757ee650        5 days ago          0 B
...
ubuntu               16.04               7aa3602ab41e        3 weeks ago         115 MB
<none>               <none>              7133c2c42ed4        3 weeks ago         0 B
...

ここではdockerイメージubuntu:16.04を用いてOpenStackのコンテナイメージを作成します。 もし自身の環境にこのイメージが存在しない場合はdocker pullにより取得して下さい。

docker saveコマンドの引数としてubuntuを指定し、 これをopenstack image createに渡すことによりイメージを作成することができます。 この例ではコンテナイメージの名前をmycontainerとしています。

$ docker save ubuntu | openstack image create mycontainer --public \
  --container-format docker --disk-format raw

openstack image listによりイメージの一覧を参照すると、mycontainerが登録されていることが確認できます。

$ openstack image list
+--------------------------------------+--------------------------+--------+
| ID                                   | Name                     | Status |
+--------------------------------------+--------------------------+--------+
| 0fe13767-ebec-4fed-90de-b4868d89d659 | cirros-0.3.5-x86_64-disk | active |
| 3ba9f988-5ae0-46a7-880d-71c5c659c460 | mycontainer              | active |
+--------------------------------------+--------------------------+--------+

コンテナの起動

それではこのイメージを用いてコンテナを起動してみましょう。 Developer Quick-Start ではping -c 4 8.8.8.8を実行して動作を確認していますが、ubuntuではpingが無いため 代わりに/bin/bashを実行してみます。 bashでの操作を行うために--interactiveを指定します。 また起動するコンテナの名前は--name testとして指定します。

zun run --image-driver glance \
  --name test \
  --interactive \
  mycontainer /bin/bash

起動が成功すると、実行結果が表示された後に以下の様なメッセージが表示されるので Enterを押してbashに移行します。終了するにはexitを実行します。

...
Waiting for container start
Waiting for container start
connected to 865323fc-f74f-49db-b8ef-abc0d31c62dd, press Enter to continue
type ~. to disconnect
root@9aefd148f29c:/# 
...
root@9aefd148f29c:/# exit

bashを抜けた後もコンテナは引き続き動作しています。zun listにより状態を確認することができます。

$ zun list
+--------------------------------------+------+-------------+---------+------------+--------------------------+-------+
| uuid                                 | name | image       | status  | task_state | addresses                | ports |
+--------------------------------------+------+-------------+---------+------------+--------------------------+-------+
| 865323fc-f74f-49db-b8ef-abc0d31c62dd | test | mycontainer | Running | None       | 172.24.4.19, 2001:db8::5 | []    |
+--------------------------------------+------+-------------+---------+------------+--------------------------+-------+

なお--image-driver glanceを指定しないと登録したイメージを参照することができずにエラーとなるため注意して下さい。

$ zun run --name test --interactive  mycontainer /bin/bash
ERROR: Image mycontainer could not be found. (HTTP 400) (Request-ID: req-79fe7422-9db4-40bb-b3e7-8bd58dcf4e05)

最後にコンテナを停止して、削除を行います。

$ zun stop test
Request to stop container test has been accepted.
$ zun delete test
Request to delete container test has been accepted.

zun listコマンドにより削除されていることが分かります。

$ zun list
+------+------+-------+--------+------------+-----------+-------+
| uuid | name | image | status | task_state | addresses | ports |
+------+------+-------+--------+------------+-----------+-------+
+------+------+-------+--------+------------+-----------+-------+

以上

python3-neovimのインストール

はじめに

neovimとdeopleteを使おうとしたらpython3-neovimが無いと怒られたので対応した話。 やり方が少しわかりにくかったのでメモ。

事象

環境はUbuntu16.04。 neovimとdeinを入れたので、 ついでにdeopleteを入れてみるとエラーがでた。

[deoplete] deoplete failed to load. Try the :Upd...mmand and restart Neovim. See also :checkhealth.

nvimを開いて:checkhealthをしてみると、確かにERROR: has("python3") was not successfulとなっている。

  2 health#deoplete#check
  3 ========================================================================
  4 ## deoplete.nvim
  5   - OK: exists("v:t_list") was successful
  6   - OK: has("timers") was successful
  7   - ERROR: has("python3") was not successful
  8     - ADVICE:
  9       - Please install the python3 package for neovim.
 10       - A good guide can be found here: https://github.com/tweekmonster/nvim
          -python-doctor/wiki/Advanced:-Using-pyenv

対処方法

apt searchで探してもpython3-neovimが無い。 pip3を使うケースか。そういや入れてなかったのでインストールする。

$ sudo apt install python3-pip

pip3python3-neovimをインストールする場合は次のようにする。

$ pip3 install neovim

あらためて:checkhealthで確認するとOK: has("python3") was successfulとなっていることがわかる。

  2 health#deoplete#check
  3 ========================================================================
  4 ## deoplete.nvim
  5   - OK: exists("v:t_list") was successful
  6   - OK: has("timers") was successful
  7   - OK: has("python3") was successful

ついでに下の方にあるpython2の方も確認してみると、こちらにもエラーがあることを発見。

 41 ## Python 2 provider (optional)
 42   - WARNING: No Python interpreter was found with the neovim module.  Using the first available for d
      iagnostics.
 43   - ERROR: Python provider error
 44     - ADVICE:
 45       - provider/pythonx: Could not load Python 2:
 46           /usr/bin/python2 does not have the "neovim" module. :help provider-python
 47           /usr/bin/python2.7 does not have the "neovim" module. :help provider-python
 48           python2.6 not found in search path or not executable.
 49           /usr/bin/python does not have the "neovim" module. :help provider-python

pip3と同じようにしてneovimをインストールする。

$ pip install neovim

再び:checkhealthで確認すると、今度は正常にインストールされていることがわかる。

 41 ## Python 2 provider (optional)
 42   - INFO: `g:python_host_prog` is not set.  Searching for python2 in the environment.
 43   - INFO: Executable: /usr/bin/python2
 44   - INFO: Python2 version: 2.7.12
 45   - INFO: python2-neovim version: 0.2.6
 46   - OK: Latest python2-neovim is installed: 0.2.6

以上

pktgenソースコード・リーディングのメモ (2)

はじめに

主にpktgenのポートの管理に興味があったので調べてみた。バージョンはpktgen-3.4.9。

ポートの設定はmain()から呼び出されるpktgen_config_ports()で実行され、この関数はapp/pktgen-port-cfg.cに定義されている。

変数とか構造体とか

void
pktgen_config_ports(void)
{
        uint32_t lid, pid, i, s, q, sid;
        rxtx_t rt;
        pkt_seq_t   *pkt;
        port_info_t     *info;
        char buff[RTE_MEMZONE_NAMESIZE];
        int32_t ret, cache_size;
        char output_buff[256] = { 0 };
        uint64_t ticks;

最初のuint32_t型の変数はそれぞれ

  • lid: lcore ID
  • pid: ポートID
  • sid: ソケットID

rxtx_tはRXとTXのそれぞれのポートIDをまとめて保持するためのunion。

/* lib/common/l2p.h */

typedef union {
        struct {
                uint16_t rx;
                uint16_t tx;
        };
        uint32_t rxtx;
} rxtx_t;

またpkt_seq_tはパケットの種別などの情報を保持する構造体。 送受信先それぞれのEthernetアドレスやIPアドレス、ポートの他、VLAN IDやToS値、CoS値など各種情報が含まれる。 app/pktgen-seq.hに定義されている。

/* app/pktgen-seq.h */

typedef struct pkt_seq_s {
        /* Packet type and information */
        struct ether_addr eth_dst_addr; /**< Destination Ethernet address */
        struct ether_addr eth_src_addr; /**< Source Ethernet address */

        struct cmdline_ipaddr ip_src_addr;      /**< Source IPv4 address also used for IPv6 */
        struct cmdline_ipaddr ip_dst_addr;      /**< Destination IPv4 address */
        uint32_t ip_mask;                       /**< IPv4 Netmask value */

        uint16_t sport;         /**< Source port value */
        uint16_t dport;         /**< Destination port value */
        uint16_t ethType;       /**< IPv4 or IPv6 */
        uint16_t ipProto;       /**< TCP or UDP or ICMP */
        uint16_t vlanid;        /**< VLAN ID value if used */
        uint8_t cos;            /**< 802.1p cos value if used */
        uint8_t tos;            /**< tos value if used */
        uint16_t ether_hdr_size;/**< Size of Ethernet header in packet for VLAN ID */

        uint32_t mpls_entry;    /**< MPLS entry if used */
        uint16_t qinq_outerid;  /**< Outer VLAN ID if Q-in-Q */
        uint16_t qinq_innerid;  /**< Inner VLAN ID if Q-in-Q */
        uint32_t gre_key;       /**< GRE key if used */

        uint16_t pktSize;       /**< Size of packet in bytes not counting FCS */
        uint16_t pad0;
        uint32_t gtpu_teid;     /**< GTP-U TEID, if UDP dport=2152 */
        uint8_t seq_enabled;    /**< Enable or disable this sequence through GUI */

        pkt_hdr_t hdr __rte_cache_aligned;      /**< Packet header data */
        uint8_t pad[MBUF_SIZE - sizeof(pkt_hdr_t)];
} pkt_seq_t __rte_cache_aligned;

port_info_tはポートの情報を保持する。主なものは以下の通り。

  • pid: ポートID
  • tx_pps: 毎秒あたりの送信量
  • vlanid, tos, cos: vlan関連の情報
  • max_latency, jitter_threshold: 遅延とかジッターとか
  • q[NUM_Q]: キュー情報
  • pcap: PCAPの情報
/* app/pktgen-port-cfg.h */

typedef struct port_info_s {
    uint16_t pid;      /**< Port ID value */
    uint16_t tx_burst; /**< Number of TX burst packets */
    double tx_rate;        /**< Percentage rate for tx packets with fractions */
    rte_atomic32_t port_flags;  /**< Special send flags for ARP and other */

    rte_atomic64_t transmit_count;  /**< Packets to transmit loaded into current_tx_count */
    rte_atomic64_t current_tx_count;/**< Current number of packets to send */
    uint64_t tx_cycles;    /**< Number cycles between TX bursts */
    uint64_t tx_pps;   /**< Transmit packets per seconds */
    uint64_t delta;        /**< Delta value for latency testing */
    uint64_t tx_count; /**< Total count of tx attempts */

    /* Packet buffer space for traffic generator, shared for all packets per port */
    uint16_t seqIdx;       /**< Current Packet sequence index 0 to NUM_SEQ_PKTS */
    uint16_t seqCnt;       /**< Current packet sequence max count */
    uint16_t prime_cnt;        /**< Set the number of packets to send in a prime command */
    uint16_t vlanid;       /**< Set the port VLAN ID value */
    uint8_t cos;           /**< Set the port 802.1p cos value */
    uint8_t tos;           /**< Set the port tos value */
    rte_spinlock_t port_lock;   /**< Used to sync up packet constructor between cores */
    pkt_seq_t *seq_pkt;     /**< Sequence of packets seq_pkt[NUM_SEQ_PKTS]=default packet */
    range_info_t range;     /**< Range Information */

    uint32_t mpls_entry;   /**< Set the port MPLS entry */
    uint32_t gre_key;  /**< GRE key if used */

    uint16_t nb_mbufs; /**< Number of mbufs in the system */
    uint16_t pad1;
    uint64_t max_latency;  /**< TX Latency sequence */
    uint64_t avg_latency;  /**< Latency delta in clock ticks */
    uint64_t min_latency;  /**< RX Latency sequence */
    uint32_t magic_errors;
    uint32_t latency_nb_pkts;
    uint64_t jitter_threshold;
    uint64_t jitter_threshold_clks;
    uint64_t jitter_count;
    uint64_t prev_latency;

    pkt_stats_t stats;  /**< Statistics for a number of stats */
    port_sizes_t sizes; /**< Stats for the different packets sizes */

    eth_stats_t init_stats; /**< Initial packet statistics */
    eth_stats_t prev_stats; /**< current port statistics */
    eth_stats_t rate_stats; /**< current packet rate statistics */
    uint64_t max_ipackets; /**< Max seen input packet rate */
    uint64_t max_opackets; /**< Max seen output packet rate */
    uint64_t max_missed;   /**< Max missed packets seen */

    struct rte_eth_link link;  /**< Link Information like speed and duplex */

    struct q_info {
        rte_atomic32_t flags;       /**< Special send flags for ARP and other */
        struct mbuf_table tx_mbufs;    /**< mbuf holder for transmit packets */
        struct rte_mempool *rx_mp; /**< Pool pointer for port RX mbufs */
        struct rte_mempool *tx_mp; /**< Pool pointer for default TX mbufs */
        struct rte_mempool *range_mp;  /**< Pool pointer for port Range TX mbufs */
        struct rte_mempool *seq_mp;    /**< Pool pointer for port Sequence TX mbufs */
        struct rte_mempool *pcap_mp;   /**< Pool pointer for port PCAP TX mbufs */
        struct rte_mempool *special_mp;    /**< Pool pointer for special TX mbufs */
        uint64_t tx_cnt, rx_cnt;
    } q[NUM_Q];

    int32_t rx_tapfd;      /**< Rx Tap file descriptor */
    int32_t tx_tapfd;      /**< Tx Tap file descriptor */
    pcap_info_t           *pcap;    /**< PCAP information header */
    uint64_t pcap_cycles;      /**< number of cycles for pcap sending */

    int32_t pcap_result;   /**< PCAP result of filter compile */
    struct bpf_program pcap_program;/**< PCAP filter program structure */

    /* Packet dump related */
    struct packet {
        void *data;    /**< Packet data */
        uint32_t len;  /**< Length of data */
    } dump_list[MAX_DUMP_PACKETS];
    uint8_t dump_head; /**< Index of last packet written to screen */
    uint8_t dump_tail; /**< Index of last valid packet in dump_list */
    uint8_t dump_count;    /**< Number of packets the user requested */

    struct rnd_bits_s     *rnd_bitfields;  /**< Random bitfield settings */

    struct rte_eth_conf port_conf;     /**< port configuration information */
    struct rte_eth_dev_info dev_info;  /**< PCI info + driver name */
    struct rte_eth_rxconf rx_conf;     /**< RX configuration */
    struct rte_eth_txconf tx_conf;     /**< TX configuration */
    ring_conf_t ring_conf;          /**< Misc ring configuration information */
    char user_pattern[USER_PATTERN_SIZE];  /**< User set pattern values */
    fill_t fill_pattern_type;       /**< Type of pattern to fill with */
} port_info_t;

ポートの初期化

システムのすべてのポートを検出する。

        /* Find out the total number of ports in the system. */
        /* We have already blacklisted the ones we needed to in main routine. */
        pktgen.nb_ports = rte_eth_dev_count();
        if (pktgen.nb_ports > RTE_MAX_ETHPORTS)
                pktgen.nb_ports = RTE_MAX_ETHPORTS;

        if (pktgen.nb_ports == 0)
                pktgen_log_panic("*** Did not find any ports to use ***");

1度に表示されるポートの数を設定する。pktgen.starting_portは画面に表示される最初のポートの番号。 pg_port_matrix_dump()はlcoreとポートの情報を画面に表示する。

        pktgen.starting_port = 0;

        /* Setup the number of ports to display at a time */
        if (pktgen.nb_ports > pktgen.nb_ports_per_page)
                pktgen.ending_port = pktgen.starting_port +
                        pktgen.nb_ports_per_page;
        else
                pktgen.ending_port = pktgen.starting_port + pktgen.nb_ports;

        pg_port_matrix_dump(pktgen.l2p);

lcoreにポートを割り当てていく。 get_map()によりそのlcoreに割り当てられているポートかどうかを確認し、 pg_set_port_private()によりそのポートを割り当てる。

        /* For each lcore setup each port that is handled by that lcore. */
        for (lid = 0; lid < RTE_MAX_LCORE; lid++) {
                if (get_map(pktgen.l2p, RTE_MAX_ETHPORTS, lid) == 0)
                        continue;

                /* For each port attached or handled by the lcore */
                for (pid = 0; pid < pktgen.nb_ports; pid++) {
                        /* If non-zero then this port is handled by this lcore. */
                        if (get_map(pktgen.l2p, pid, lid) == 0)
                                continue;
                        pg_set_port_private(pktgen.l2p, pid, &pktgen.info[pid]);
                        pktgen.info[pid].pid = pid;
                }
        }
        pg_dump_l2p(pktgen.l2p);

pg_set_port_private()は与えられたpktgen.l2pのポートに対して、 プライベートポインタとしてpktgen.info[pid]を割り与える。

/* lib/common/l2p.h */

static __inline__ void
pg_set_port_private(l2p_t *l2p, uint16_t pid, void *ptr)
{
        pobj_t    *pobj = &l2p->ports[pid];

        pobj->private = ptr;
}

ポートの情報を保持するpobj_tは次のように定義されており、privateをメンバーとして持つ。

/* lib/common/l2p.h */

typedef struct {
        uint16_t pid;
        uint16_t rx_qid;
        uint16_t tx_qid;
        uint16_t nb_lids;
        uint16_t lids[RTE_MAX_LCORE];
        void      *private;
} pobj_t;       /* ports pointer lcores */

ポートの設定

プライベートポインタの取得

get_map()によりlcoreが割り当てられているかどうか確認し、 先ほど設定したプライベートポインタを取得する。

        for (pid = 0; pid < pktgen.nb_ports; pid++) {
                /* Skip if we do not have any lcores attached to a port. */
                if ( (rt.rxtx = get_map(pktgen.l2p, pid, RTE_MAX_LCORE)) == 0)
                        continue;

                pktgen.port_cnt++;
                ...

                info = get_port_private(pktgen.l2p, pid);

get_port_private()の定義は以下の通り。

/* lib/common/l2p.h */

 static __inline__ void *
 get_port_private(l2p_t *l2p, uint16_t pid)
 {
         pobj_t    *pobj = &l2p->ports[pid];
 
         return pobj->private;
 }

スピンロックやパケットバッファの初期化

info変数の持つポートのロックやパケットを初期化する。 info->seq_pktは一連のパケットを転送するために参照されるパケットヘッダの構造体。 rte_zmalloc_socket()はNUMAソケットを指定して0で初期化した領域をmemzoneに確保するための関数。

                rte_spinlock_init(&info->port_lock);

                /* Create the pkt header structures for transmitting sequence of packets. */
                snprintf(buff, sizeof(buff), "seq_hdr_%d", pid);
                info->seq_pkt = rte_zmalloc_socket(buff,
                                                   (sizeof(pkt_seq_t) * NUM_TOTAL_PKTS),
                                                   RTE_CACHE_LINE_SIZE, rte_socket_id());

                 for (i = 0; i < NUM_TOTAL_PKTS; i++)
                         info->seq_pkt[i].seq_enabled = 1;

infoのその他のメンバーの初期化も行うが省略。

ポートの設定

pktgen_port_conf_setup()によりpidで指定したポートの RXやTX、ringのセットアップのほかハードウェアに関するパラメータなどの各種設定を行う。 デフォルト値の設定はdefualt_port_confを用いる。 そのあとrte_eth_dev_configure()を呼び出してRX、TXデバイスのセットアップを行う。

                pktgen_port_conf_setup(pid, &rt, &default_port_conf);

                if ( (ret = rte_eth_dev_configure(pid, rt.rx, rt.tx, &info->port_conf)) < 0)
                        pktgen_log_panic(
                                "Cannot configure device: port=%d, Num queues %d,%d (%d)%s",
                                pid, rt.rx, rt.tx, errno, rte_strerror(-ret));

default_port_confはポートの各種設定を保持するための構造体。

const struct rte_eth_conf default_port_conf = {
        .rxmode = {
                .split_hdr_size = 0,
                .header_split   = 0,    /**< Header Split disabled. */
                .hw_ip_checksum = 0,    /**< IP checksum offload disabled. */
                .hw_vlan_filter = 0,    /**< VLAN filtering enabled. */
                .hw_vlan_strip  = 0,    /**< VLAN strip enabled. */
                .hw_vlan_extend = 0,    /**< Extended VLAN disabled. */
                .jumbo_frame    = 0,    /**< Jumbo Frame Support disabled. */
                .hw_strip_crc   = 0,    /**< CRC stripping by hardware disabled. */
        },
        .rx_adv_conf = {
                .rss_conf = {
                        .rss_key = NULL,
                        .rss_key_len = 0,
                        .rss_hf = ETH_RSS_IP,
                },
        },
        .txmode = {
                .mq_mode = ETH_MQ_TX_NONE,
        },
};

ケットシーケンスの設定

                pkt = &info->seq_pkt[SINGLE_PKT];

                pktgen.mem_used = 0;

RXの設定

ポートの情報からソケットIDを取得し、RXのバッファを作成する。 バッファはinfo->q[q].rx_mpに代入される。

                for (q = 0; q < rt.rx; q++) {
                        /* grab the socket id value based on the lcore being used. */
                        sid = rte_lcore_to_socket_id(get_port_lid(pktgen.l2p, pid, q));

                        /* Create and initialize the default Receive buffers. */
                        info->q[q].rx_mp = pktgen_mbuf_pool_create("Default RX", pid, q,
                                                                   info->nb_mbufs, sid, cache_size);
                        if (info->q[q].rx_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Default RX mbufs", pid);

取得したmbufをrte_eth_rx_queue_setup()に与えてRXキューのセットアップを行う。

                        ret = rte_eth_rx_queue_setup(pid, q, pktgen.nb_rxd, sid,
                                                     &info->rx_conf, pktgen.info[pid].q[q].rx_mp);
                        if (ret < 0)
                                pktgen_log_panic("rte_eth_rx_queue_setup: err=%d, port=%d, %s",
                                                 ret, pid, rte_strerror(-ret));

最後にrte_eth_dev_set_rx_queue_stats_mapping()によりRXキューを統計情報の対象に追加する。

                        lid = get_port_lid(pktgen.l2p, pid, q);
                        pktgen_log_info("      Set RX queue stats mapping pid %d, q %d, lcore %d\n", pid, q, lid);
                        rte_eth_dev_set_rx_queue_stats_mapping(pid, q, lid);
                }
                pktgen_log_info("");

TXの設定

ポートの情報からソケットIDを取得し、4つのTXのバッファを作成する。

  • default TX
  • range TX
  • sequence TX
  • special TX
                for (q = 0; q < rt.tx; q++) {
                        /* grab the socket id value based on the lcore being used. */
                        sid = rte_lcore_to_socket_id(get_port_lid(pktgen.l2p, pid, q));

                        /* Create and initialize the default Transmit buffers. */
                        info->q[q].tx_mp = pktgen_mbuf_pool_create("Default TX", pid, q,
                                                                   MAX_MBUFS_PER_PORT, sid, cache_size);
                        if (info->q[q].tx_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Default TX mbufs", pid);

                        /* Create and initialize the range Transmit buffers. */
                        info->q[q].range_mp = pktgen_mbuf_pool_create("Range TX", pid, q,
                                                                      MAX_MBUFS_PER_PORT, sid, 0);
                        if (info->q[q].range_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Range TX mbufs", pid);

                        /* Create and initialize the sequence Transmit buffers. */
                        info->q[q].seq_mp = pktgen_mbuf_pool_create("Sequence TX", pid, q,
                                                                    MAX_MBUFS_PER_PORT, sid, cache_size);
                        if (info->q[q].seq_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Sequence TX mbufs", pid);

                        /* Used for sending special packets like ARP requests */
                        info->q[q].special_mp = pktgen_mbuf_pool_create("Special TX", pid, q,
                                                                        MAX_SPECIAL_MBUFS, sid, 0);
                        if (info->q[q].special_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Special TX mbufs", pid);

あとpcapの設定も行う。しかしここではpcap_mp(pcap TX mbuf)に関する処理はしておらず、最後に行う。

                        /* Setup the PCAP file for each port */
                        if (pktgen.info[pid].pcap != NULL)
                                if (pktgen_pcap_parse(pktgen.info[pid].pcap, info, q) == -1)
                                        pktgen_log_panic("Cannot load PCAP file for port %d", pid);
                        /* Find out the link speed to program the WTHRESH value correctly. */
                        pktgen_get_link_status(info, pid, 0);

取得したmbufをrte_eth_tx_queue_setup()に与えてTXキューのセットアップを行う。

                        ret = rte_eth_tx_queue_setup(pid, q, pktgen.nb_txd, sid, &info->tx_conf);
                        if (ret < 0)
                                pktgen_log_panic("rte_eth_tx_queue_setup: err=%d, port=%d, %s",
                                                 ret, pid, rte_strerror(-ret));
                        pktgen_log_info("");
                }
                pktgen_log_info("%*sPort memory used = %6lu KB", 71, " ",
                                (pktgen.mem_used + 1023) / 1024);

source mac addressの設定などを行ってポートの初期化は終了。

                /* Grab the source MAC addresses */
                rte_eth_macaddr_get(pid, &pkt->eth_src_addr);
                pktgen_log_info("%s,  Src MAC %02x:%02x:%02x:%02x:%02x:%02x",
                                output_buff,
                                pkt->eth_src_addr.addr_bytes[0],
                                pkt->eth_src_addr.addr_bytes[1],
                                pkt->eth_src_addr.addr_bytes[2],
                                pkt->eth_src_addr.addr_bytes[3],
                                pkt->eth_src_addr.addr_bytes[4],
                                pkt->eth_src_addr.addr_bytes[5]);

                /* Copy the first Src MAC address in SINGLE_PKT to the rest of the sequence packets. */
                for (i = 0; i < NUM_SEQ_PKTS; i++)
                        ethAddrCopy(&info->seq_pkt[i].eth_src_addr, &pkt->eth_src_addr);

バイスの処理を開始

        /* Start up the ports and display the port Link status */
        for (pid = 0; pid < pktgen.nb_ports; pid++) {
                if (get_map(pktgen.l2p, pid, RTE_MAX_LCORE) == 0)
                        continue;

                /* Start device */
                if ( (ret = rte_eth_dev_start(pid)) < 0)
                        pktgen_log_panic("rte_eth_dev_start: port=%d, %s",
                                         pid, rte_strerror(-ret));
                rte_delay_us(250000);
        }

ポートのスタートアップ

        /* Start up the ports and display the port Link status */
        for (pid = 0; pid < pktgen.nb_ports; pid++) {
                if (get_map(pktgen.l2p, pid, RTE_MAX_LCORE) == 0)
                        continue;

                info = get_port_private(pktgen.l2p, pid);

                pktgen_get_link_status(info, pid, 1);

                if (info->link.link_status)
                        snprintf(output_buff, sizeof(output_buff),
                                 "Port %2d: Link Up - speed %u Mbps - %s",
                                 pid, (uint32_t)info->link.link_speed,
                                 (info->link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
                                 ("full-duplex") : ("half-duplex"));
                else
                        snprintf(output_buff, sizeof(output_buff), "Port %2d: Link Down", pid);

                /* If enabled, put device in promiscuous mode. */
                if (pktgen.flags & PROMISCUOUS_ON_FLAG) {
                        strncatf(output_buff, " <Enable promiscuous mode>");
                        rte_eth_promiscuous_enable(pid);
                }

                pktgen_log_info("%s", output_buff);
                pktgen.info[pid].seq_pkt[SINGLE_PKT].pktSize = MIN_PKT_SIZE;

                /* Setup the port and packet defaults. (must be after link speed is found) */
                for (s = 0; s < NUM_TOTAL_PKTS; s++)
                        pktgen_port_defaults(pid, s);

                pktgen_range_setup(info);

                rte_eth_stats_get(pid, &info->init_stats);

                pktgen_rnd_bits_init(&pktgen.info[pid].rnd_bitfields);
        }

パケットキャプチャの設定

ポートごとにパケットキャプチャの設定を行う。

        /* Setup the packet capture per port if needed. */
        for (sid = 0; sid < coremap_cnt(pktgen.core_info, pktgen.core_cnt, 0); sid++)
                pktgen_packet_capture_init(&pktgen.capture[sid], sid);

pktgen_packet_capture_init()はパケットキャプチャの初期化を行いmemzoneの確保を行う。 この関数はapp/pktgen-capture.cにて定義されている。

/* app/pktgen-capture.c */

void
pktgen_packet_capture_init(capture_t *capture, int socket_id)
{
        char memzone_name[RTE_MEMZONE_NAMESIZE];

        if (!capture)
                return;

        capture->lcore  = RTE_MAX_LCORE;
        capture->port   = RTE_MAX_ETHPORTS;
        capture->used   = 0;

        snprintf(memzone_name, sizeof(memzone_name), "Capture_MZ_%d",
                 socket_id);
        capture->mz = rte_memzone_reserve(memzone_name, CAPTURE_BUFF_SIZE,
                        socket_id, RTE_MEMZONE_1GB | RTE_MEMZONE_SIZE_HINT_ONLY);
}

pktgenソースコード・リーディングのメモ (1)

はじめに

主にpktgenのパケット生成や受信に興味があったので調べてみた。 まずはメイン関数から調べてみる。 バージョンはpktgen-3.4.9。

main()概要

main()はapp/pktgen-main.cにある。以下おもな手順。

シグナルハンドラの登録

SIGSEGV、SIGHUP、SIGINT、SIGPIPEの4種類を登録する。

        signal(SIGSEGV, sig_handler);
        signal(SIGHUP, sig_handler);
        ...

pktgenオブジェクトの初期化

memset()を用いてpktgenを0で埋める。 次にデフォルト値を入れていく。

        memset(&pktgen, 0, sizeof(pktgen));

        pktgen.flags            = PRINT_LABELS_FLAG;
        pktgen.ident            = 0x1234;
        pktgen.nb_rxd           = DEFAULT_RX_DESC;
        pktgen.nb_txd           = DEFAULT_TX_DESC;
        pktgen.nb_ports_per_page = DEFAULT_PORTS_PER_PAGE;

pktgenはアプリケーション自体の情報を保持するpktgen_t構造体のインスタンスであり、実体はapp/pktgen.hのpktgen_s構造体として定義されている。 pktgenの持つ属性には、CLIの情報を持つcmdlineやLua Stateなどのポインタ、ポートやlcoreなどの情報が含まれる。

/* app/pktgen.h */

typedef struct pktgen_s {
        struct cmdline *cl;     /**< Command Line information pointer */
        void *L;                /**< Lua State pointer */
        char *hostname;         /**< GUI hostname */

        int32_t socket_port;            /**< GUI port number */
        uint32_t blinklist;             /**< Port list for blinking the led */
        uint32_t flags;                 /**< Flag values */
        uint16_t ident;                 /**< IPv4 ident value */
        uint16_t last_row;              /**< last static row of the screen */
        uint16_t nb_ports;              /**< Number of ports in the system */
        uint8_t starting_port;          /**< Starting port to display */
        uint8_t ending_port;            /**< Ending port to display */
        uint8_t nb_ports_per_page;      /**< Number of ports to display per page */

        uint16_t nb_rxd;        /**< Number of receive descriptors */
        uint16_t nb_txd;        /**< Number of transmit descriptors */
        uint16_t portNum;       /**< Current Port number */
        uint16_t port_cnt;      /**< Number of ports used in total */
        uint64_t hz;            /**< Number of events per seconds */

        int (*callout)(void *callout_arg);
        void *callout_arg;

        struct rte_pci_addr blacklist[RTE_MAX_ETHPORTS];
        struct rte_pci_addr portlist[RTE_MAX_ETHPORTS];
        uint8_t *portdesc[RTE_MAX_ETHPORTS];
        uint32_t portdesc_cnt;
        uint32_t blacklist_cnt;

        /* port to lcore mapping */
        l2p_t *l2p;

        port_info_t info[RTE_MAX_ETHPORTS];     /**< Port information */

        lc_info_t core_info[RTE_MAX_LCORE];
        uint16_t core_cnt;
        uint16_t pad0;
        lscpu_t *lscpu;
        char *uname;
        eth_stats_t cumm_rate_totals;   /**< port rates total values */
        uint64_t max_total_ipackets;    /**< Total Max seen input packet rate */
        uint64_t max_total_opackets;    /**< Total Max seen output packet rate */

        pthread_t thread;       /**< Thread structure for Lua server */

        uint64_t counter;       /**< A debug counter */
        uint64_t mem_used;      /**< Display memory used counters per ports */
        uint64_t total_mem_used;/**< Display memory used for all ports */
        int32_t argc;   /**< Number of arguments */
        char *argv[64]; /**< Argument list */
        capture_t capture[RTE_MAX_NUMA_NODES];  /**< Packet capture, 1 struct per socket */
        uint8_t is_gui_running;
} pktgen_t;

lcoreとポートのマッピングを初期化

pktgenではlcoreとポートとの組み合わせを構造体として保持しており、最初にこれを初期化する。

        if ( (pktgen.l2p = l2p_create()) == NULL)
                pktgen_log_panic("Unable to create l2p");

l2p_tはlcoreとポートとのマッピングを保持する構造体で、次の様に定義される。

/* lib/common/l2p.c */

typedef struct {
        volatile uint8_t stop[RTE_MAX_LCORE];
        lobj_t lcores[RTE_MAX_LCORE];
        pobj_t ports[RTE_MAX_ETHPORTS];
        rxtx_t map[MAX_MAP_PORTS][MAX_MAP_LCORES];
} l2p_t;

またdefineの値は以下の様になっている。

/* lib/common/l2p.h */

#define    MAX_MAP_PORTS           (RTE_MAX_ETHPORTS + 1)
#define MAX_MAP_LCORES         (RTE_MAX_LCORE + 1)

画面、ログ、EAL、CLIの初期化

        /* Initialize the screen and logging */
        pktgen_init_log();
        pktgen_cpu_init();

        /* initialize EAL */
        ret = rte_eal_init(argc, argv);
        ...

        if (pktgen_cli_create())
                return -1;

pktgen_cli_create()はapp/cli-functions.cに定義されている。 この関数はさらにlib/cli/cli.cで定義されたcli_create()を呼び出し、そこからcli_init()を呼び出し、実際の初期化を行う。

lua関連の処理

pktgenはLua stateをpktgen.Lにて管理する。 _lua_openlibLuaのライブラリを読み込んだり種々の変数を設定したりする。 またlua_newlib_add()はLua自体に独自に追加拡張された関数(lib/lua/lua-5.3.4.patchにて定義されている)。

        lua_newlib_add(_lua_openlib);
        cli_set_lua_callback(pktgen_lua_dofile);

        /* Open the Lua script handler. */
        if ( (pktgen.L = lua_create_instance()) == NULL) {
                pktgen_log_error("Failed to open Lua pktgen support library");
                return -1;
        }
        cli_set_user_state(pktgen.L);

pktgenのオプション解析

        /* parse application arguments (after the EAL ones) */
        ret = pktgen_parse_args(argc, argv);
        if (ret < 0)
                return -1;

ここは特に興味が無いので省略。

master lcoreの初期化など

マスターのlcoreが他の処理に割り当てられていないかチェックする。

        i = rte_get_master_lcore();
        if (get_lcore_rxcnt(pktgen.l2p, i) || get_lcore_txcnt(pktgen.l2p, i)) {
                cli_printf("*** Error can not use master lcore for a port\n");
                cli_printf("    The master lcore is %d\n", rte_get_master_lcore());
                exit(-1);
        }

あとHZ、画面描画関連、copyrightの表示などを行っていく。

        pktgen.hz = rte_get_timer_hz(); /* Get the starting HZ value. */

        scrn_create_with_defaults(pktgen.flags & ENABLE_THEME_FLAG);

        rte_delay_ms(100);      /* Wait a bit for things to settle. */

        print_copyright(PKTGEN_APP_NAME, PKTGEN_CREATED_BY);

        pktgen_log_info(
                ...

ポートの初期化

pktgen_config_ports()を呼び出す。この関数の内容は別の記事で書く。

他のlcoreの割当

workerスレッドにタスクを与えて起動していく。 pktgen_launch_one_lcore()はapp/pktgen.cで定義されており、 lcoreが担当するポートがRXもしくはTXのいずれか(もしくは両方)を判定して適切な処理を実行させる。

        /* launch per-lcore init on every lcore except master and master + 1 lcores */
        for (i = 0; i < RTE_MAX_LCORE; i++) {
                if ( (i == rte_get_master_lcore()) || !rte_lcore_is_enabled(i) )
                        continue;
                ret = rte_eal_remote_launch(pktgen_launch_one_lcore, NULL, i);
                if (ret != 0)
                        pktgen_log_error("Failed to start lcore %d, return %d", i, ret);
        }

pktgen_launch_one_lcore(app/pktgen.c)の内容。 それぞれのタスクの内容は名前の通り。 詳細は別の記事で書く予定。

int
pktgen_launch_one_lcore(void *arg __rte_unused)
{
        uint8_t lid = rte_lcore_id();

        if (pktgen_has_work())
                return 0;

        rte_delay_ms((lid + 1) * 21);

        switch (get_type(pktgen.l2p, lid)) {
        case RX_TYPE:               pktgen_main_rx_loop(lid);       break;
        case TX_TYPE:               pktgen_main_tx_loop(lid);       break;
        case (RX_TYPE | TX_TYPE):   pktgen_main_rxtx_loop(lid);     break;
        }
        return 0;
}

CLIの描画関連の処理など

最後に描画関連の処理を行ってCLIの待ち受けを開始する。

        /* Disable printing log messages of level info and below to screen, */
        /* erase the screen and start updating the screen again. */
        pktgen_log_set_screen_level(LOG_LEVEL_WARNING);
        scrn_erase(this_scrn->nrows);

        splash_screen(3, 16, PKTGEN_APP_NAME, PKTGEN_CREATED_BY);

        scrn_resume();

        pktgen_clear_display();

        rte_timer_setup();

        if (pktgen.flags & ENABLE_GUI_FLAG) {
                if (!scrn_is_paused() ) {
                        scrn_pause();
                        scrn_cls();
                        scrn_setw(1);
                        scrn_pos(this_scrn->nrows, 1);
                }

                lua_init_socket(pktgen.L,
                                &pktgen.thread,
                                pktgen.hostname,
                                pktgen.socket_port);
#ifdef GUI
                pktgen_gui_main(argc, argv);
#endif
        }

        pktgen_cli_start();

終了化処理

CLIを抜けると終了化処理を実行して終了。

        execute_lua_close(pktgen.L);

        pktgen_stop_running();

        scrn_pause();

        scrn_setw(1);
        scrn_printf(100, 1, "\n");      /* Put the cursor on the last row and do a newline. */
        scrn_destroy();

        /* Wait for all of the cores to stop running and exit. */
        rte_eal_mp_wait_lcore();

        for (i = 0; i < pktgen.nb_ports; i++) {
                rte_eth_dev_stop(i);
                rte_delay_ms(100);
                rte_eth_dev_close(i);
        }

        cli_destroy();

        return 0;

DPDKにパッチを送るためのメモ

はじめに

DPDKにパッチを送るのに'git send-email'を使う必要があるのですが、慣れるまでにすぐ忘れてしまったり、間違うと変なパッチを送って取り返しがつかなくなってしまうので、自分のためにメモしておきます。
参考にする場合は自己責任でお願いします。

とりあえずUbuntuで試した結果を書きます(コミットメッセージのエディタはvim)。他の環境で試してみて違うところがあったら追記します(TODO)。

パッチを作成する

`git format-patch`を使うと1コミットごとにパッチファイルを作成することができます。また複数のパッチファイルをまとめて作成することも可能です。
複数のパッチファイルを送付する場合、カバーレターと呼ばれる要約を記述するためのファイルを最初に追加するようにします。これはパッチの受け取り手に対して理解を促すためで、「いきなり沢山パッチを送られても、、、」となるのを避けるためのものです。

単に最新1コミットのパッチを作成する

対象のコミットと出力先ディレクトリを指定します。ディレクトリが存在しない場合は作成してくれます。
これは最新のコミット('-1'で指定)を'patches'ディレクトリへ出力する例です。

$ git format-patch -1 -o patches
patches/0001-....patch

複数のパッチを作成する

先ほどの'-1'を変更することで、最新のコミットからさかのぼって複数のパッチを作成することができます。
カバーレターを付けて最新3コミットのパッチファイルを作成するには次のようにします。

$ git format-patch -3 -o patches --cover-letter
patches/0000-cover-letter.patch
patches/0001-....patch
patches/0002-....patch
patches/0003-....patch

'0000-cover-letter.patch'がカバーレターです。このファイルはパッチを送付する前に編集する必要があります。
'*** SUBJECT HERE ***'と'*** BLURB HERE ***'の2箇所を削除して、タイトルと説明文を追記します。これらを削除しないと'git send-email'での送信時にエラーとなります。

From a8ed80b425c7a81fab8dc72a7ce1aba9aa9d... Mon Sep 17 00:00:00 2001
From: [作成者の名前] <[メールアドレス]>
Date: Sun, 18 Feb 2018 00:10:02 +0900
Subject: [PATCH 0/3] *** SUBJECT HERE *** 

*** BLURB HERE ***

[作成者の名前] ([パッチ数]):
...

任意のコミットのパッチを作成する

前の例でパッチ数を指定した'-1'や'-3'ではなく、コミットIDを指定することにより任意のコミットのパッチファイルを作成することもできます。
例えば以下のようにしてコミットIDを確認した場合、4番目の'7f7cfda'を指定することで最新3つのパッチを作成できます。

git log --oneline
a8ed80b ...
3c9d1c5 ...
8627ed9 ...
7f7cfda ...
4b60fc2 ...
...

出力されるパッチファイルは先ほどと同じです。パッチ数が多い場合にはいちいち数えるよりもこちらのほうが簡単です。

$ git format-patch 7f7cfda -o patches --cover-letter
patches/0000-cover-letter.patch
patches/0001-....patch
patches/0002-....patch
patches/0003-....patch

また2つのコミットIDを指定すれば、その間のコミットだけを出力することもできます。例えば最新から2、3番めのコミットだけを用いる場合は'7f7cfda..3c9d1c5'(4番目..2番目)を指定します。

git format-patch -o patches0 7f7cfda..3c9d1c5 --cover-letter
patches/0000-cover-letter.patch
patches/0001-....patch
patches/0002-....patch

パッチを送付する前に

必ずdevtools/checkpatches.shを使いましょう。もし警告やエラーが出力された場合は、該当箇所を修正してコミットし、再度パッチを作成します。
問題がなければ以下のようになります。

$ /path/to/checkpatches.sh patches/*

3/3 valid patches

パッチを送付する(1回目)

'git send-email'によりパッチを送付します。新規にパッチを送付するには宛先とパッチファイルを指定します。またディレクトリを指定することで一度にすべてのパッチを送付することもできます。

$ git send-email --to [宛先アドレス] --annotate patches/

'--annotate'は送信する前にパッチの確認と編集を行うためのオプションです。送信前に確認できていれば特に必要はありませんが、チェック漏れを防ぐ意味でも付与しておくほうが良いです。

宛先が複数ある場合は','で区切ることで指定可能です。また'--to'だけでなく'--cc'なども使用できます。

$ git send-email --to aaa@example.com,bbb@example.com --cc ccc@example.com --annotate patches/

パッチを送付する(2回目以降)

送付したパッチに何らかの不備があり修正を求められることもあります。このような場合、新しく別のパッチを送付するのではなく、バージョンを付与して更新パッチであることを明記します。

更新パッチの作成

例えばバグを修正してコミットを訂正した場合、'git format-patch'にオプション'-v2'を追加してパッチを作成します。作成されたパッチには'v2-'という文字列が追加されています。カバーレターの編集を忘れないようにします。

$ git format-patch -3 -o patches --cover-letter -v2
patches/v2-0000-cover-letter.patch
patches/v2-0001-....patch
...

さらに変更が必要な場合は、バージョンを'-v3'、'-v4'のように更新していきます。

更新パッチの送付

更新パッチは以前のメールへのリプライとして送付します。'--in-reply-to'オプションは以前のメールを特定するためのもので、ここにメッセージIDを指定します。

$ git send-email --to [宛先アドレス] --annotate --in-reply-to [メッセージID] patches/

以前のメールのメッセージIDを確認するにはソースを参照する必要があります。'Subject'のすぐ後ろ辺りに'Message-Id: <...>'という行がありますので、'<>'で囲まれた文字列をメッセージIDとして用います。

...
Subject: [PATCH 0/2] Update patches for ...
Date: Sun, 18 Feb 2018 00:54:39 +0900
Message-Id: <...>
...

(参考)パッチ作成と送付を一度に行う

checkpatches.shでのチェックを飛ばすことになるので禁じ手ですが、'git send-email'から一度に両方行うことも可能です。
例えば最新の3コミットをカバーレターを付けて送付する場合は以下のようにします。

$ git send-email -3 -to [宛先アドレス] --cover-letter --annotate

カバーレターの編集画面が開きますので、'Subject:'の'*** SUBJECT HERE ***'と本文の'*** BLURB HERE ***'を両方とも削除して、それぞれメッセージを追記します。

カバーレターの編集を終えてエディタを終了しようとすると、'--annotate'を指定してるので「編集すべきファイルがあと3個あります」と言われます。
パッチファイルは'/tmp'の一時ディレクトリに作成されていて、必要に応じてこれらを開いて編集を続けます。もし編集が不要なら、そのままもう一度エディタを終了します。
編集が終わるとメール送信のメッセージが表示されます。

...
Send this email? ([y]es|[n]o|[q]uit|[a]ll): 

最後にyesもしくはallで送信します。

以上