- raw socket收报全丢掉, 测试性能
- scanf忽略某个filed
- IO模型
- 父子进程与FD -- fcntl的使用
- 使用gcc内建函数返回调用者
- trace_printk()很有意思
- Uboot的命令注册机制
- C实现基类派生
- 把rootfs.cpio嵌入到vmlinux--incbin的使用
- 如何用C设计面向对象的中间层
- syslog记录log到syslogd
- 使用宏拼接
1. raw socket收报全丢掉, 测试性能
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <netinet/in.h>
#include <string.h>
extern unsigned if_nametoindex(const char *ifname);
char *interface = "eth-mgnt";
int create_raw_eth_socket(void)
{
int eth_sock;
struct sockaddr_ll eth_dest;
unsigned long itf_ifindex = if_nametoindex(interface);
/*ETH socket, RAW communication*/
eth_dest.sll_family = PF_PACKET;
eth_dest.sll_protocol = htons(ETH_P_ALL);
eth_dest.sll_ifindex = itf_ifindex;
eth_dest.sll_hatype = ARPHRD_ETHER;
eth_dest.sll_pkttype = PACKET_OTHERHOST;
eth_dest.sll_halen = ETH_ALEN;
eth_sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
bind(eth_sock,(struct sockaddr*)ð_dest,sizeof(eth_dest));
return eth_sock;
}
int main(int argc,char *argv[], char**envp)
{
int eth_sock;
struct sockaddr_ll eth_src;
unsigned char data[1600];
unsigned long packet_count = 0;
eth_sock = create_raw_eth_socket();
while (1)
{
socklen_t addr_len = sizeof(eth_src);
int size = recvfrom(eth_sock,data,sizeof(data),0,(struct sockaddr*)ð_src,&addr_len);
if (size < 0) {
printf("Error reading socket\n\r");
break;
}
packet_count++;
if ((packet_count % 1000) == 0)
printf("count = %10lu\n",packet_count);
}
close(eth_sock);
return 1;
}
2. scanf忽略某个filed
%*
关键词告诉scanf忽略这部分input
fscanf(fpstat, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu"
"%lu %ld %ld %*d %*d %*d %*d %*u %lu %ld",
&result->utime_ticks, &result->stime_ticks,
&result->cutime_ticks, &result->cstime_ticks, &result->vsize,
&rss)
3. IO模型
3.1. 普通阻塞读写
正常的读写是阻塞的.
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t nbytes);
3.2. 非阻塞系统调用
非阻塞的读写需要先给fd设置标记
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
后面的读写会马上返回, 如果没准备好, 返回EAGAIN.
3.3. 多路IO复用
int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);
除了标准的 select 函数之外,操作系统中还提供了一个比较相似的 poll 函数,它使用链表存储文件描述符,摆脱了 1024 的数量上限。
3.4. O_ASYNC和O_NONBLOCK有什么区别?
异步编程模型下, 一般设socket的fd为O_NONBLOCK, 结合epoll, 等待数据的时候阻塞在epoll, 写socket不阻塞, 有错误直接返回.
那O_ASYNC有什么用? 和O_NONBLOCK有什么区别?
答:O_ASYNC请求kernel在有数据的时候发送SIGIO or SIGPOLL给所属进程. 一般不常用, 因为在sighandler里面处理io不好, signal的deliver过程比epoll复杂.
4. 父子进程与FD -- fcntl的使用
子进程集成父进程的FD, 但子进程exec后呢? 有个函数, 如果调用, 表示子进程exec后, 关闭该fd. 意思就是子进程即使exec, 默认也是继承父进程的fd的.
fcntl(fd, F_SETFD, FD_CLOEXEC)
fcntl还有其他作用:
#include<unistd.h>
#include<fcntl.h>
//定义函数
int fcntl(int fd , int cmd);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd,int cmd,struct flock * lock);
//fcntl()用来操作文件描述词的一些特性。参数fd代表欲设置的文件描述词,参数cmd代表欲操作的指令。
//有以下几种情况:
F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参数fd的文件描述词。执行成功则返回新复制的文件描述词。请参考dup2()。F_GETFD取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。
F_GETFL 取得文件描述词状态旗标,此旗标为open()的参数flags。
F_SETFL设置文件描述词状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES或EAGAIN。
F_SETLKW F_SETLK作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。参数lock指针为flock结构指针,定义如下
struct flcok
{
short int l_type;
short int l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
};
l_type 有三种状态:
F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定
l_whence 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。
//返回值
成功则返回0,若有错误则返回-1,错误原因存于errno.
5. 使用gcc内建函数返回调用者
在kernel启动过程中, 有下面的打印:
memblock_reserve: [0x00000100000000-0x00000100829fff] flags 0x0 early_init_devtree+0x104/0x350
memblock_reserve: [0x00000000c10000-0x0000000166fdc7] flags 0x0 early_init_fdt_scan_reserved_mem+0x5c/0x7c
memblock_reserve: [0x000001fe000000-0x000001feffffff] flags 0x0 memblock_alloc_range_nid+0x70/0x7c
看起来memblock_reserve
是个函数, 表示哪段区间的内存被reserve了. 它的最后一个打印字段有意思, 是调用者的函数名吗?
打印出自:
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
memblock_dbg("memblock_reserve: [%#016llx-%#016llx] flags %#02lx %pF\n",
(unsigned long long)base,
(unsigned long long)base + size - 1,
0UL, (void *)_RET_IP_);
return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}
而_RET_IP_
是#define _RET_IP_ (unsigned long)__builtin_return_address(0)
__builtin_return_address
是gcc提供的内建函数, 返回当前函数的返回地址. level0表示当前函数, level1表示返回其父函数的返回地址, 以此类推.Built-in Function: void * __builtin_return_address (unsigned int level)
详见: https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html
有了程序地址, printk传入%pS
就能打印符号了. 见linux/Documentation/core-api/printk-formats.rst
5.1. printk支持多种内部结构打印
实际上, printk支持很多很多常见格式的打印, 比如IP地址(%pI4
), MAC地址格式的打印, 物理地址phys_addr_t
类型的(%pa
), DMA地址...
还有一些linux内部常用结构, 比如dentry, block_device, device tree, 时间, clock等等等等.
6. trace_printk()很有意思
在代码里加了trace_printk(), 和ftrace连用, 可以很方便的调试代码. 而且, 有个地方很有意思, 你调用了trace_printk(), 在kernel启动的很早期, 会打印一个warning message
这个地方非常早, trace_printk()还没来得及被调用.
那kernel怎么知道要打印这个呢? 如果代码里没有调用trace_printk(), 是没有这个打印的
这个warning是在trace_printk_init_buffers()
打印的, 条件是if (__stop___trace_bprintk_fmt != __start___trace_bprintk_fmt)
这两个"变量"是指针:
extern const char *__start___trace_bprintk_fmt[];
extern const char *__stop___trace_bprintk_fmt[];
在链接脚本里include/asm-generic/vmlinux.lds.h
#define TRACE_PRINTKS() VMLINUX_SYMBOL(__start___trace_bprintk_fmt) = .; \
*(__trace_printk_fmt) /* Trace_printk fmt' pointer */ \
VMLINUX_SYMBOL(__stop___trace_bprintk_fmt) = .;
OK, 再来看看trace_printk()
的定义:
原来, trace_printk()的入参fmt, 是保存在__trace_printk_fmt
这个section的, 用trace_printk_fmt指针指向它.
如果有代码调用了trace_printk(), 那就传了fmt参数, 从而__trace_printk_fmt
里面有东西, 这是在编译时就知道了的事情;
那在运行时判断, 这个section里面有没有东西if (__stop___trace_bprintk_fmt != __start___trace_bprintk_fmt)
6.1. 结论
这是个很好的技巧, 根据编译时代码的状态, 来决定运行时的行为.
7. Uboot的命令注册机制
要点: 这样可以免去在运行时"注册"命令. 使用连接器的自定义section里面的数组, 命令静态的被连接到可执行程序里. 这样做, 可以在一个C文件里, 同时完成命令的实现, 和命令的注册. 这个特殊的section, 可以在运行时访问.
比如bootm命令, 在common/cmd_bootm.c
中, 是这样注册的:
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"boot application image from memory", bootm_help_text
);
这个宏, 会把bootm相关的要素: 命令, 函数, 说明, 写入特殊的段内, 需要链接器配合. linux kernel也经常用这个机制.
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp);
关键在于include/linker_lists.h
中:
#define ll_entry_declare(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_"#_list"_2_"#_name)))
可以在运行时访问, 因为可以找到数组的"基地址"
struct my_sub_cmd *msc = ll_entry_start(struct my_sub_cmd, cmd_sub);
#define ll_entry_start(_type, _list) \
({ \
static char start[0] __aligned(4) __attribute__((unused, \
section(".u_boot_list_2_"#_list"_1"))); \
(_type *)&start; \
})
8. C实现基类派生
关键点: C里面, 派生类的结构体, 显式的在最开始包含其基类的结构体.
这样, 在知道一个指针是基类, 但实际是派生类的情况下, 可以指针强转成派生类.
使用gcc提供的类型检查, 可以保证这个强转的合法性.
8.1. 同类型检查
GNU提供了一个runtime宏来检查两个类型是否一样:
int __builtin_types_compatible_p(type_a, type_b);
type_a和type_b一致, 返回1; 否则返回0
使用
#define __COMPARE_TYPES(v, t) \
__builtin_types_compatible_p(__typeof__(v), t)
...
if (__COMPARE_TYPES(p, double) != 0)
err(EX_DATAERR, "invalid type");
8.2. 实例
以上代码简化为
struct stack_entry *field = (struct stack_entry *) iter->ent
8.2.1. 基类和派生类
field
是stack_entry
, 而iter->ent
是trace_entry
, 是不一样的. 为什么能强转呢?
这中间的奥秘在于, straceentry相当于基类, stack_entry是它的派生类.
strace_entry被stack_entry包含在其结构体头部.
具体见kernel/trace/trace.h
![](img/c编程杂记高级篇_20221011161311.png)
这样, 指向基类的指针, 被强制转换为指向其派生类的指针. 这要求trace_stack_print
的入参struct trace_iterator *iter
, 本身就是stack_entry派生类.
9. 把rootfs.cpio嵌入到vmlinux--incbin的使用
在linux-octeon-3.0/usr/initramfs_data.S
中
使用了.incbin
其中, INITRAMFS_IMAGE为initramfs_data.cpio.gz
#include <linux/stringify.h>
#include <asm-generic/vmlinux.lds.h>
.section .init.ramfs,"a"
__irf_start:
.incbin __stringify(INITRAMFS_IMAGE)
__irf_end:
.section .init.ramfs.info,"a"
.globl VMLINUX_SYMBOL(__initramfs_size)
VMLINUX_SYMBOL(__initramfs_size):
#ifdef CONFIG_64BIT
.quad __irf_end - __irf_start
#else
.long __irf_end - __irf_start
#endif
10. 如何用C设计面向对象的中间层
10.1. 父类子类公用一个结构体
设计一个结构体, 并声明这个结构体的全局变量: 这个结构体有点像C++的类, 但可以认为是父类和子类都共用这个结构体, 所以, 这个结构体应该设计成既包括公有方法的指针, 又包括私有方法的指针.
typedef struct
{
一些控制变量;
//父类方法指针, 由父类C文件提供默认值;
void (*lock)(void);
void (*unlock)(void);
int (*reset)(int stop_core);
uint64_t (*read_register)(int core, int reg);
void (*write_register)(int core, int reg, uint64_t value);
uint64_t (*read_csr)(uint64_t physical_address);
void (*write_csr)(uint64_t physical_address, uint64_t value);
//子类方法, 分别由子类C文件提供
int (*open)(const char *remote_spec);
void (*close)(void);
void (*read_mem)(void *buffer, uint64_t physical_address, int length);
void (*write_mem)(uint64_t physical_address, const void *buffer, int length);
uint64_t (*get_running_cores)(void);
uint32_t (*get_num_cores)(void);
} octeon_remote_funcs_t;
/*全局变量*/
static octeon_remote_funcs_t remote_funcs;
10.2. 一个公共C文件, 和若干个"子类"C文件
父类:
octeon-remote.c
子类:
octeon-remote-cvmx.c
octeon-remote-gdbremote.c
octeon-remote-map.c
octeon-remote-console.c
octeon-remote-debug-handler.c
octeon-remote-linux.c
octeon-remote-pci.c
父类C文件提供了对外统一的api(虚函数), 使调用者不用关心底层实现, 特别的, 在子类C文件中, 虽然是有具体的"虚函数"的实现, 但最好还是调用父类的虚函数接口.
父类C文件负责总的初始化, 包括公用结构体变量的父类方法指针的赋值, 并根据情况(如用户输入), 来调用某个子类的初始化接口.
子类的初始化接口里, 负责子类特殊的初始化, 以及子类方法指针的赋值.
10.3. 总结
这种方式实现的封装, 要求全部子类的代码都要参与编译. 只是运行阶段, 根据情况, 选择使用某个子类.
11. syslog记录log到syslogd
#include <syslog.h>
setlogmask (LOG_UPTO (LOG_NOTICE));
openlog ("exampleprog", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
syslog (LOG_NOTICE, "Program started by User %d", getuid ());
syslog (LOG_INFO, "A tree falls in a forest");
closelog ();
12. 使用宏拼接
- 定义寄存器表board_cpld_registers.h, 注意不要加头文件卫士
cpld_declare_reg(CIPHER1, 0x00, 0, 8)
cpld_declare_reg(CIPHER2, 0x01, 0, 8)
cpld_declare_reg(CIPHER3, 0x02, 0, 8)
cpld_declare_reg(CIPHER4, 0x03, 0, 8)
cpld_declare_reg(VERSION, 0x07, 0, 8)
cpld_declare_reg(REVISION, 0x08, 0, 8)
cpld_declare_reg(REG_GICI_SHELF_BPSW, 0x0b, 0, 8)
cpld_declare_reg(SHELF_ID, 0x0b, 0, 2)
cpld_declare_reg(BPSW, 0x0b, 4, 3)
cpld_declare_reg(GICI_P, 0x0b, 7, 1)
cpld_declare_reg(REG_NTR_STAT, 0x0e, 0, 8)
- 在公共头文件里定义宏接口, 使用宏拼接##来简化操作
extern spinlock_t cpld_lock;
#undef cpld_declare_reg
#define cpld_declare_reg(a,b,c,d) a,
enum cpld_field_id {
#include "board_cpld_registers.h"
};
#undef cpld_declare_reg
#define cpld_declare_reg(a,b,c,d) a##_OFFSET_ = b,
enum cpld_field_offset {
#include "board_cpld_registers.h"
};
#undef cpld_declare_reg
#define cpld_declare_reg(a,b,c,d) a##_START_BIT_ = c,
enum cpld_field_start_bit {
#include "board_cpld_registers.h"
};
#undef cpld_declare_reg
#define cpld_declare_reg(a,b,c,d) a##_MASK_ = ((1 << (d)) - 1) << (c),
enum cpld_field_mask {
#include "board_cpld_registers.h"
};
#define rd_cpld_field(base, field_nr, pval) \
do { \
unsigned long __flags; \
u8 __tmp; \
spin_lock_irqsave(&cpld_lock, __flags); \
__tmp = ioread8((base) + field_nr##_OFFSET_); \
spin_unlock_irqrestore(&cpld_lock, __flags); \
*(pval) = (__tmp & field_nr##_MASK_) >> field_nr##_START_BIT_; \
}while(0)
#define wr_cpld_field(base, field_nr, val) \
do { \
unsigned long __flags; \
u8 __tmp; \
spin_lock_irqsave(&cpld_lock, __flags); \
__tmp = ioread8((base) + field_nr##_OFFSET_); \
__tmp &= (u8)~field_nr##_MASK_; \
__tmp |= ((val) << field_nr##_START_BIT_) & field_nr##_MASK_; \
iowrite8(__tmp, (base) + field_nr##_OFFSET_); \
spin_unlock_irqrestore(&cpld_lock, __flags); \
}while(0)