星期五, 4月 17, 2020

C: variable argument lists

函數可有未知數目和 type 的引數。

#include <stdarg.h>

void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);

首先宣告一個 va_list 物件,然後使用 va_start() 初始化物件,和 va_end() 成對使用,之間用 va_arg()、或 vprintf() 等使用 va_list 物件。
va_arg() 將第一個引數定義為 type 後指到下個引數。

#include <stdarg.h>

void my_printf(const char *fmt, ...)
{
    va_list ap;        //宣告一個 va_list 物件 ap
 
    va_start(ap, fmt); //初始化 ap
    vprintf(fmt, ap);  //使用 ap 並更新指到下個未用的引數
    va_end(ap);        //結束 ap
}

va_list 最普遍是一個指標指到函數的 stack frame,複製可以直接指定:

va_list aq = ap

但也有系統是一個陣列,內容是一個指標,需要:

va_list aq;
*aq = *ap;

另外,argument 如果是透過暫存器傳遞的系統,va_start() 需要配置記憶體存 arguments 和指示哪個是下個 argument,va_end() 可以釋出配置的記憶體。

因應不同情況, C99 新增 va_copy(),複製變成:

va_list aq;
va_copy(aq, ap);
...
va_end(aq);

星期六, 4月 11, 2020

Linux signals

signal 通知 process 事件發生,或稱為軟體中斷,和硬體中斷一樣中斷程式正常流程。signal 來源可能是

  1. 另一個 process,可作為同步機制或 IPC 原型。
  2. 自己
  3. kernel
    1. hardare exception,如不對的機器指令、除以 0、存取非法位址。
    2. 終端機特殊字元,如中斷字元 Ctrl-C、suspend 字元 Ctrl-Z。
    3. software event,收到 file 輸入、視窗改變大小、timer 時間到、超出 CPU 使用時間、child process 結束。

每個 signal 用 1 開始的小數字表示,在 signal.h 名稱定義為 SIGxxx。signal 分成兩類,一是傳統的標準 signal,在 Linux 是 1~31。另一種 realtime signal

signal 在排程準備要執行時處理,如果已經執行中則馬上處理。signal 從產生 (generated) 到送達 (delivered) 之間是 pending。process 可設遮蔽 (mask) 來 block 特定 signal 而讓其產生後一直 pending,直到 unblock。每個 thread 有自己的 signal mask,pthread_sigmask() 設定 thread 哪些 signal blocked。傳統 single-threaded 應用,sigprocmask() 可用來設定 signal mask (用在 multi-thread 會怎樣?)。fork() 繼承 signal mask,execve() 保留 signal mask (繼承和保留有何不同?)。

signal 送達時執行下列預設「動作」(Action) 之一:

  • Ign:忽略,process 不會發現。
  • Term:process 結束 (killed),是 abnormal process termination,有別於 exit() 的 normal process termination。
  • Core:process core dump 並結束。core dump 包含 process 的 virtual memory image,可作為除錯用。
  • Stop:process 暫停。
  • Cont:暫停的 process 繼續。
Signal動作 (預設)
說明
SIGHUP1Termkill. 控制終端機偵測到 Hangup 或控制行程 death。
SIGINT2Term中斷字元 Ctrl-C 產生。
SIGQUIT3CoreQuit from keyboard
SIGILL4CoreIllegal Instruction
SIGABRT6Coreabort() 產生,用來產生 core dump。
SIGFPE8CoreFloating point exception
SIGKILL9TermKill signal (無法 caught、blocked、或 ignored)
SIGSEGV11CoreInvalid memory reference
SIGPIPE13TermBroken pipe: write to pipe with no readers
SIGALRM14TermTimer signal from alarm(2)
SIGTERM15TermTermination signal
SIGUSR130,10,16TermUser-defined signal 1
SIGUSR231,12,17TermUser-defined signal 2
SIGCHLD20,17,18IgnChild stopped or terminated
SIGCONT19,18,25ContContinue if stopped
SIGSTOP17,19,23StopStop process (無法 caught、blocked、或 ignored)
SIGTSTP18,20,24StopStop typed at terminal
SIGTTIN21,21,26StopTerminal input for background process
SIGTTOU22,22,27StopTerminal output for background process

可用 sigaction() 或 signal() 改變動作,或稱為設定 disposition,可選擇 1) 回復預設動作、2) 忽略、或 3) 執行 signal handler。執行 signal handler 時,稱為 caught。如果想要 signal handler 結束 process,可執行 exit()。如果想要 signal handler core dump 後結束 process,可執行 abort()。signal 的動作是 process 屬性,其下 thread 共用。fork() 繼承。execve() 時,除了忽略的 signal 外,其它回復預設動作。

/proc/PID/status 包含 hex 表示的 bit-mask fields 查看 signal 狀態

SigPnd:per-thread pending

ShdPnd:process-wide pending

SigBlk:blocked

SigIgn:ignored

SigCgt:caught

signal 可以是 process-directed 或 thread-directed。process-directed 的 signal 是 targeted 整個 process,來自 kernel other than a hardware exception,或 kill() 或 sigqueue()。thread-directed 的 signal 是 targeted 特定 thread,這些是來自 hardware  exception (如 invalid memory access 產生 SIGSEGV,math error 產生 SIGFPE) 或使用 tgkill() 或 pthread_kill()。process-directed signal 任意由一個沒 block 的 thread 處理。thread 可 sigpending() 取得目前 pending 的 signal,包含 process-directed 和給自己 thread-directed。fork() 一開始 pending signal set 是空的,execve() 保留 pending signal set。

standard signal 各自只有一個,沒有特定處理順序。pending 時再發生也只有一個。

signal handler 預設使用 the normal process stack (是指 thread stack?process 有 stack?),可用 sigaltstack() 改用不同的 stack。

送 signal
  • raise():送 signal 給自己 thread。
  • kill():送 signal 給指定的 process、指定的 process group、或所有 processe。
  • killpg():送 signal 給指定的 process group。
  • pthread_kill():送 signal 給 process 內指定的 thread。
  • tgkill():實作 pthread_kill() 的系統呼叫。
  • sigqueue():送 real-time signal 及 accompanying data 給指定的 process。
等 signal
  • 系統呼叫 pause():暫停執行直到補抓到任何 signal。
  • 系統呼叫 sigsuspend():暫時改變 signal mask 並暫停執行直到補抓到任何 unmasked 的 signal。
  • 系統呼叫 sigwaitinfo()、sigtimedwait()、和 sigwait():暫停執行直到補抓到指定的 signal 並回傳 signal 資訊。
  • 系統呼叫 signalfd():回傳 file descriptor 用來讀有關 signal 的資訊,read() 後等候指定的 signal 並得到描述 signal 的資料結構。

可以用 pthread_sigmask() 或 sigprocmask() 設 mask 來讓 signal pending 進不來,直到 unmask。用 sigpending() 查看 pending siganl。


程序管理

INT (Ctrl-C)
TERM (kill 預設)
WINCH
SEGV
EXIT
USR1
USR2
ALARM
HUP

Real-time signals:範圍定義在 SIGRTMIN 和 SIGRTMAX 之間,Linux kernel 支援 32 到 64,但 glibc POSIX thread 實作只用 2 (NPTL) 或 3 (LinuxThreads) 個。real-time signal 運用由應用程式定義,沒有事先定義,預設動作是 Term。real-time signal 的不同...

參考

  1. man 7 signal
  2. http://www.gnu.org/software/libc/manual/html_node/Signal-Handling.html
  3. busybox ping
  4. busybox dhcpc:用 SIGUSR1 來更新 IP 租約, 用 SIGUSR2 來釋出 IP 租約。
  5. TLPI §20.1

星期六, 4月 04, 2020

struct net_device

網路通訊需要網路介面卡存取通訊媒介,struct net_device 提供存取網路一致的界面,包括網卡驅動程式,是 character 和 block 外第三種 Linux 核心的 device,但不出現在 /dev 目錄。網卡可以是實體的或虛擬的 (如 loopback),也可能綁定協定 (如 PPP)。虛擬網路界面通常透過 priv 實作,例如 bonding。

重要成員
  • mtu:例如 Ethernet 是 1500,可透過「ifconfig <interface> mtu <mtu>」更改
  • flags:可透過 ifconfig 看到 UP BROADCAST RUNNING MULTICAST NOARP
  • dev_addr[]:MAC 位址
  • promiscuity:設為 promiscuous 模式的次數
  • IN_DEV 開頭巨集 (IN_DEV_FORWARD(), IN_DEV_RX_REDIRECTS())
  • hard_start_xmit():傳送封包的方式
  • ip_ptr
  • struct in_device 成員 cnf (ipv4_devconf) 包括 forwarding, accept_redirects, send_redirects 等,對應到 /proc/sys/net/ipv4/conf/all/???。
struct net_device {
 char   name[IFNAMSIZ];
 struct netdev_name_node *name_node;
 struct dev_ifalias __rcu *ifalias;
 /*
  * I/O specific fields
  * FIXME: Merge these and struct ifmap into one
  */
 unsigned long  mem_end;
 unsigned long  mem_start;
 unsigned long  base_addr;
 int   irq;

 /*
  * Some hardware also needs these fields (state,dev_list,
  * napi_list,unreg_list,close_list) but they are not
  * part of the usual set specified in Space.c.
  */

 unsigned long  state;

 struct list_head dev_list;
 struct list_head napi_list;
 struct list_head unreg_list;
 struct list_head close_list;
 struct list_head ptype_all;
 struct list_head ptype_specific;

 struct {
  struct list_head upper;
  struct list_head lower;
 } adj_list;

 netdev_features_t features;
 netdev_features_t hw_features;
 netdev_features_t wanted_features;
 netdev_features_t vlan_features;
 netdev_features_t hw_enc_features;
 netdev_features_t mpls_features;
 netdev_features_t gso_partial_features;

 int   ifindex;
 int   group;

 struct net_device_stats stats;

 atomic_long_t  rx_dropped;
 atomic_long_t  tx_dropped;
 atomic_long_t  rx_nohandler;

 /* Stats to monitor link on/off, flapping */
 atomic_t  carrier_up_count;
 atomic_t  carrier_down_count;

#ifdef CONFIG_WIRELESS_EXT
 const struct iw_handler_def *wireless_handlers;
 struct iw_public_data *wireless_data;
#endif
 const struct net_device_ops *netdev_ops;
 const struct ethtool_ops *ethtool_ops;
#ifdef CONFIG_NET_L3_MASTER_DEV
 const struct l3mdev_ops *l3mdev_ops;
#endif
#if IS_ENABLED(CONFIG_IPV6)
 const struct ndisc_ops *ndisc_ops;
#endif

#ifdef CONFIG_XFRM_OFFLOAD
 const struct xfrmdev_ops *xfrmdev_ops;
#endif

#if IS_ENABLED(CONFIG_TLS_DEVICE)
 const struct tlsdev_ops *tlsdev_ops;
#endif

 const struct header_ops *header_ops;

 unsigned int  flags;
 unsigned int  priv_flags;

 unsigned short  gflags;
 unsigned short  padded;

 unsigned char  operstate;
 unsigned char  link_mode;

 unsigned char  if_port;
 unsigned char  dma;

 /* Note : dev->mtu is often read without holding a lock.
  * Writers usually hold RTNL.
  * It is recommended to use READ_ONCE() to annotate the reads,
  * and to use WRITE_ONCE() to annotate the writes.
  */
 unsigned int  mtu;
 unsigned int  min_mtu;
 unsigned int  max_mtu;
 unsigned short  type;
 unsigned short  hard_header_len;
 unsigned char  min_header_len;

 unsigned short  needed_headroom;
 unsigned short  needed_tailroom;

 /* Interface address info. */
 unsigned char  perm_addr[MAX_ADDR_LEN];
 unsigned char  addr_assign_type;
 unsigned char  addr_len;
 unsigned char  upper_level;
 unsigned char  lower_level;
 unsigned short  neigh_priv_len;
 unsigned short          dev_id;
 unsigned short          dev_port;
 spinlock_t  addr_list_lock;
 unsigned char  name_assign_type;
 bool   uc_promisc;
 struct netdev_hw_addr_list uc;
 struct netdev_hw_addr_list mc;
 struct netdev_hw_addr_list dev_addrs;

#ifdef CONFIG_SYSFS
 struct kset  *queues_kset;
#endif
 unsigned int  promiscuity;
 unsigned int  allmulti;


 /* Protocol-specific pointers */

#if IS_ENABLED(CONFIG_VLAN_8021Q)
 struct vlan_info __rcu *vlan_info;
#endif
#if IS_ENABLED(CONFIG_NET_DSA)
 struct dsa_port  *dsa_ptr;
#endif
#if IS_ENABLED(CONFIG_TIPC)
 struct tipc_bearer __rcu *tipc_ptr;
#endif
#if IS_ENABLED(CONFIG_IRDA) || IS_ENABLED(CONFIG_ATALK)
 void    *atalk_ptr;
#endif
 struct in_device __rcu *ip_ptr;
#if IS_ENABLED(CONFIG_DECNET)
 struct dn_dev __rcu     *dn_ptr;
#endif
 struct inet6_dev __rcu *ip6_ptr;
#if IS_ENABLED(CONFIG_AX25)
 void   *ax25_ptr;
#endif
 struct wireless_dev *ieee80211_ptr;
 struct wpan_dev  *ieee802154_ptr;
#if IS_ENABLED(CONFIG_MPLS_ROUTING)
 struct mpls_dev __rcu *mpls_ptr;
#endif

/*
 * Cache lines mostly used on receive path (including eth_type_trans())
 */
 /* Interface address info used in eth_type_trans() */
 unsigned char  *dev_addr;

 struct netdev_rx_queue *_rx;
 unsigned int  num_rx_queues;
 unsigned int  real_num_rx_queues;

 struct bpf_prog __rcu *xdp_prog;
 unsigned long  gro_flush_timeout;
 rx_handler_func_t __rcu *rx_handler;
 void __rcu  *rx_handler_data;

#ifdef CONFIG_NET_CLS_ACT
 struct mini_Qdisc __rcu *miniq_ingress;
#endif
 struct netdev_queue __rcu *ingress_queue;
#ifdef CONFIG_NETFILTER_INGRESS
 struct nf_hook_entries __rcu *nf_hooks_ingress;
#endif

 unsigned char  broadcast[MAX_ADDR_LEN];
#ifdef CONFIG_RFS_ACCEL
 struct cpu_rmap  *rx_cpu_rmap;
#endif
 struct hlist_node index_hlist;

/*
 * Cache lines mostly used on transmit path
 */
 struct netdev_queue *_tx ____cacheline_aligned_in_smp;
 unsigned int  num_tx_queues;
 unsigned int  real_num_tx_queues;
 struct Qdisc  *qdisc;
 unsigned int  tx_queue_len;
 spinlock_t  tx_global_lock;

 struct xdp_dev_bulk_queue __percpu *xdp_bulkq;

#ifdef CONFIG_XPS
 struct xps_dev_maps __rcu *xps_cpus_map;
 struct xps_dev_maps __rcu *xps_rxqs_map;
#endif
#ifdef CONFIG_NET_CLS_ACT
 struct mini_Qdisc __rcu *miniq_egress;
#endif

#ifdef CONFIG_NET_SCHED
 DECLARE_HASHTABLE (qdisc_hash, 4);
#endif
 /* These may be needed for future network-power-down code. */
 struct timer_list watchdog_timer;
 int   watchdog_timeo;

 struct list_head todo_list;
 int __percpu  *pcpu_refcnt;

 struct list_head link_watch_list;

 enum { NETREG_UNINITIALIZED=0,
        NETREG_REGISTERED, /* completed register_netdevice */
        NETREG_UNREGISTERING, /* called unregister_netdevice */
        NETREG_UNREGISTERED, /* completed unregister todo */
        NETREG_RELEASED,  /* called free_netdev */
        NETREG_DUMMY,  /* dummy device for NAPI poll */
 } reg_state:8;

 bool dismantle;

 enum {
  RTNL_LINK_INITIALIZED,
  RTNL_LINK_INITIALIZING,
 } rtnl_link_state:16;

 bool needs_free_netdev;
 void (*priv_destructor)(struct net_device *dev);

#ifdef CONFIG_NETPOLL
 struct netpoll_info __rcu *npinfo;
#endif

 possible_net_t   nd_net;

 /* mid-layer private */
 union {
  void     *ml_priv;
  struct pcpu_lstats __percpu  *lstats;
  struct pcpu_sw_netstats __percpu *tstats;
  struct pcpu_dstats __percpu  *dstats;
 };

#if IS_ENABLED(CONFIG_GARP)
 struct garp_port __rcu *garp_port;
#endif
#if IS_ENABLED(CONFIG_MRP)
 struct mrp_port __rcu *mrp_port;
#endif

 struct device  dev;
 const struct attribute_group *sysfs_groups[4];
 const struct attribute_group *sysfs_rx_queue_group;

 const struct rtnl_link_ops *rtnl_link_ops;

 /* for setting kernel sock attribute on TCP connection setup */
#define GSO_MAX_SIZE  65536
 unsigned int  gso_max_size;
#define GSO_MAX_SEGS  65535
 u16   gso_max_segs;

#ifdef CONFIG_DCB
 const struct dcbnl_rtnl_ops *dcbnl_ops;
#endif
 s16   num_tc;
 struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE];
 u8   prio_tc_map[TC_BITMASK + 1];

#if IS_ENABLED(CONFIG_FCOE)
 unsigned int  fcoe_ddp_xid;
#endif
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
 struct netprio_map __rcu *priomap;
#endif
 struct phy_device *phydev;
 struct sfp_bus  *sfp_bus;
 struct lock_class_key qdisc_tx_busylock_key;
 struct lock_class_key qdisc_running_key;
 struct lock_class_key qdisc_xmit_lock_key;
 struct lock_class_key addr_list_lock_key;
 bool   proto_down;
 unsigned  wol_enabled:1;

 struct list_head net_notifier_list;
};

eth_mangle_rx (OpenWrt 特有?)

許多網路界面是 PCI devices 會使用 PCI 通用的函數如 pci_register_driver() 和 pci_enable_device()。有些是 USB devices。驅動程式可見 ldd3 chap17。

註:bonding:兩個以上網路界面使用相同 IP,作為 load balancing 及 high availability 使用。
參考來源:
  1. Linux Kernel Networking by Rami Rosen at Haifux, August 2007
延伸閱讀
http://www.haifux.org/lectures/187/netLec3.pdf

星期三, 4月 01, 2020

RS232 isolation

https://sites.google.com/site/progic2/iso_rs232_pic1.jpg
https://www.analog.com/en/products/adum226n.html