顯示具有 linux 標籤的文章。 顯示所有文章
顯示具有 linux 標籤的文章。 顯示所有文章

2015/04/30

OpenMP in linux

1. Introduction


寫 project euler 的時候, 有些問題想不到速度快的解法, 只好用平行運算來加速
平常程式都以一條 thread 在執行, 要開多條 thread 來跑, 在管理上就很麻煩
OpenMP 讓人可以平行處理, 又可以減少管理的麻煩

2. 對 for 做 parallel


底下是範例程式

#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel for
 for (int i=0; i<6; i++) {
  printf("%d ", i);
 }
 printf("\r\n");

 return 0;
}

然後compile它, 這邊使用c99來compile:

c99 -o openmp_example_01 openmp_example_01.c -fopenmp

幾個重點
  • 在 header 檔要加入 omp.h
  • 在 for 迴圈前加上 #pragma omp, 這個是用來使用omp的功能, 後面是控制敘述
  • compile 的時候加上 -fopenmp
結果可以看到
4 0 1 5 2 3

每次執行的結果都不一樣

3. 語法


它的語法大改長這樣

#pragma omp <directive> [clause[[,] clause] ...]

前個例子裡, 其實 parallel 和 for 都是 directive 的敘述, 所以它可以拆成兩個
#pragma omp parallel
{
 #pragma omp for
 for (int i=0; i<6; i++) {
  printf("%d ", i);
 }
}

可以看 wiki 上面的指令清單
OpenMP in wiki

4. thread information


如果要知道目前的 thread number, 可以用 omp_get_thread_num()
#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel for
 for (int i=0; i<6; i++) {
  printf("T:%d i:%d\r\n", omp_get_thread_num(), i );
 }

 return 0;
}

執行結果
T:2 i:4
T:1 i:2
T:1 i:3
T:0 i:0
T:0 i:1
T:3 i:5

可以看到我的電腦上, 它跑了4條thread, 其中 thread 0處理 i 是 0 & 1的情況, thread 1 處理 i 是 2 & 3 的情況....

5. 控制 thread number


可以用 num_threads(n) 來控制要開多少 thread
#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel for num_threads(2)
 for (int i=0; i<6; i++) {
  printf("T:%d i:%d\r\n", omp_get_thread_num(), i );
 }

 return 0;
}

執行結果可以看到只開2條thread了
T:1 i:3
T:1 i:4
T:1 i:5
T:0 i:0
T:0 i:1
T:0 i:2

6. 手動切割 section 做平行處理


#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel sections
 {
  #pragma omp section
  {
   printf("T:%d section 0\r\n", omp_get_thread_num());
  }
  #pragma omp section
  {
   printf("T:%d section 1\r\n", omp_get_thread_num());
  }
  #pragma omp section
  {
   printf("T:%d section 2\r\n", omp_get_thread_num());
  }
 }

 return 0;
}

執行結果
T:2 section 0
T:3 section 1
T:0 section 2

要注意的是 section 之間不可以有相依性, 不然要額外做處理

7. for 裡面處理共同變數


要注意的是 parallel for 只會將 section 裡的變數做平行處理,
section 外的變數則會當成是所有thread 共用的變數,
要讓 openmp知道 section 裡的變數是每個thread自己有一份的話, 則要加上private

#include <stdio.h>
#include <omp.h>

int main() {

 int i, j;

 #pragma omp parallel for private ( j )
 for (i=0; i<2; i++) {
  for (j=0; j<2; j++) {
   printf("(%d,%d)\r\n", i, j);
  }
 }

 return 0;
}

如果不加上private, 那麼當外面的 thread 在做處理時,
就有可能不小心把別的 thread 的變數 j 加了 1

如果希望 section 裡面每個 thread 共用一份變數,
並且希望它不會有處理先後順序造成的問題, 可以使用 atomic

#include <stdio.h>
#include <omp.h>

int main() {

 int count = 0;
 #pragma omp parallel for
 for (int i=0; i<10; i++) {
  #pragma omp atomic
  count++;
 }
 printf("count: %d\r\n", count);

 return 0;
}

如果沒有加上 atomic 的話, count 最後的結果就有可能不是10







2014/09/16

Netlink

1. Introduction

Netlink 提供 userspace & kernel 之間的溝通, 它定義在 RFC 3549 "Linux Netlink as an IP Services Protocol", 相較於其它溝通方式, 它有這些優點:

  • 不用 polling
  • 可以從 kernel 當作訊息發起端
  • 提供 multicast
kernel 裡相關的檔案:

  • Netlink core files
    • net/netlink/af_netlink.c
    • include/linux/netlink.h
    • net/netlink/genetlink.c
  • Route netlink files
    • net/core/rtnetlink.c
    • include/linux/rtnetlink.h
  • Generic netlink files
    • net/netlink/genetlink.c
    • include/linux/genetlink.h

2. Create netlink socket

userspace & kernel 在create socket 時, 最後都會 call __netlink_create(), 唯一的差別是, kernel會加上屬性 NETLINK_KERNEL_SOCKET


2.1 create netlink socket in userspace


底下是sample code

int sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

struct sockaddr_nl addr;
bzero (&addr, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_IPV4_ROUTE;

bind(sockfd,(struct sockaddr *)&addr,sizeof(addr);

其中 sockaddr_nl 的定義是

struct sockaddr_nl {
    __kernel_sa_family_t  nl_family;  /* AF_NETLINK */
    unsigned short        nl_pad;     /* zero */
    __u32                 nl_pid;     /* port ID */
    __u32                 nl_groups;  /* multicast groups mask */
};
include/linux/netlink.h)

其中
nl_family 填 AF_NETLINK
nl_pad 通常得填 0
nl_pid 代表 netlink 的 unicast address, 如果是 kernel space 就填 0, 如果是 userspace, 在bind的時候就會被填上 user process 的 pid, 但如果 user process 跳過 bind, 那麼就得自行管理這個欄位的值
nl_groups 填這個 socket 所屬的 multicast group


2.2 create netlink socket in kernel

kernel 的 netlink 是在 init 的時候就會呼叫, 不同的group它的init的點也不一樣, 底下是ROUTE的init的點

static int __net_init rtnetlink_net_init(struct net *net)
{
    struct sock *sk;
    sk = netlink_kernel_create(net, NETLINK_ROUTE, RTNLGRP_MAX,
                               rtnetlink_rcv, &rtnl_mutex, THIS_MODULE);
    if (!sk)
        return -ENOMEM;
    net->rtnl = sk;
    return 0;
}
( net/core/rtnetlink.c )

其中 rtnetlink_rcv 是callback function, 負責處理從userspace來的data
NETLINK_ROUTE 是  netlink protocol, 定義在 include/linux/netlink.h
RTNLGRP_MAX 是 route multicast group, 定義在 include/linux/rtnetlink.h

netlink_kernel_create 實作在這裡

netlink_kernel_create(struct net *net, int unit, unsigned int groups,
                      void (*input)(struct sk_buff *skb),
                      struct mutex *cb_mutex, struct module *module)
( net/netlink/af_netlink.c )

netlink protocol 除了 route 之外還有很多, 比如說 kobject 也有自己 init 的點

static int uevent_net_init(struct net *net) {
        struct uevent_sock *ue_sk;

        ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL);
        if (!ue_sk)
                return -ENOMEM;

        ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT,
                                          1, NULL, NULL, THIS_MODULE);
        if (!ue_sk->sk) {
                printk(KERN_ERR
                       "kobject_uevent: unable to create netlink socket!\n");
                kfree(ue_sk);
                return -ENODEV;
        }
        mutex_lock(&uevent_sock_mutex);
        list_add_tail(&ue_sk->list, &uevent_sock_list);
        mutex_unlock(&uevent_sock_mutex);
        return 0;
}
( lib/kobject_uevent.c )

可以看到它的 netlink protocol 是 NETLINK_KOBJECT_UEVENT, 因為 kobject 沒有 multicast group, 所以 group 的欄位填 1, callback填null因為它不需要處理從 userspace 來的 data, mutex 填null 表示使用 default 的 mutex

2.3 maintain netlink socket


許多不同 netlink protocol 的 kernel socket 用 netlink_table 來maintain, 它是個link list
struct netlink_table {
    struct nl_pid_hash hash;
    struct hlist_head mc_list;
    struct listeners __rcu *listeners;
    unsigned int nl_nonroot;
    unsigned int groups;
    struct mutex *cb_mutex;
    struct module *module;
    int registered;
};

要操作這張 table, 有底下幾個 operation

  • nl_table_lock()
  • netlink_lookup()
  • netlink_insert()
比如說, 當 kobject netlink socket 開起來之後, 就會被放進這張 table 裡

2.4 register maintain netlink socket

在開完 socket 之後, 就要註冊在不同的message要什麼callback

rtnetlink_init() {
// ......
    rtnl_register(PF_UNSPEC, RTM_GETLINK, rtnl_getlink, rtnl_dump_ifinfo, rtnl_calcit);
    rtnl_register(PF_UNSPEC, RTM_SETLINK, rtnl_setlink, NULL, NULL);
// ......

rtnl_register 定義如下

rtnl_register(int protocol, int msgtype, // protocol = protocol family, ref include/linux/socket.h
    rtnl_doit_func doit,     // for addition/deletion/modification
    rtnl_dumpit_func dumpit, // for retrieving information
    rtnl_calcit_func calcit) // for calculation of buffer size

從上面例子可以看到 RTM_GETLINK 有填對應的 callback, RTM_SETLINK 有些則填 null

而實際上, rtnl_register 用 array 存對應的callback

static struct rtnl_link *rtnl_msg_handlers[RTNL_FAMILY_MAX + 1];
struct rtnl_link {
    rtnl_doit_func    doit;
    rtnl_dumpit_func  dumpit;
    rtnl_calcit_func  calcit;
};

3. sending rtnetlink message

舉個例子, 當netwrok dev oepn之後, 要通知userspace


3.1 netlink message format

netlink message 前面有 nlmsghdr 可供判斷這段message用的 netlink protocol 是什麼, 接著它的payload 則會依照不同的 netlink protocl 而有不同的format, 但通常都使用TLV的格式


通常一段 netlink message, 可以包含多份 message, 每份 message 前面都有 nlmsghdr, 裡面有一些資訊像是 len 表示這段 message 的長度, type 表示netlink protocol, 也可以利用nlmsghdr和macro幫忙指到對的位置

裡面的 payload, 如果是 route netlink protocol, 它的 format 長這樣

可以看到它的 payload 前面有個 rtmsg 的標頭, 進一步提供 route 相關的 message, 其後接著 TLV 格式的資訊

底下有個例子, 假如收到一包 route 相關 message, 以 buffer 表示其中每段資料的內容


4. Generic Netlink protocol

因為 netlink protocol 只用1 byte來儲存, 所以只有32種 netlink protocol  family 可以用, 如果加新的 netlink protocol family, 很快就不夠用, 再加上每次加上新的, header檔就得重新定義, 所以提出 generic netlink protocol

它的作法就是定義 generic netlink protocol 在 netlink protocol family, 再利用mux來區分

5. libnl.so

在實作上, 拆開每包 netlink message 還是很麻煩, 可以使用 libnl.so
它提供了
  • Connecting/disconnectng of sockets
  • Sending/receiving of data
  • Construction/parsing of messages
  • provides a customizeable receiving state machine
  • provides a abstract data type framework
這些功能對應到這些 library
  • generic netlink family (libnl-genl)
  • routing family (libnl-route)
  • netfilter family (libnl-nf)
底下是libnl的官網架構圖:


底下是它的實作範例