MFDYCS's Blog - MFDYCS
type: channel
link: https://mfdycs.cn/author/1/
description: MFDYCS
items: 【DPDK】响应ICMP echo请求
items: 【DPDK】接收ARP请求并响应
items: 【DPDK】接收并转发UDP数据
items: 【DPDK】接收UDP数据
items: 探坑记录：WSL2 搭建 Web 服务（有深坑）
items: 【Linux/UNIX系统编程】进程
items: 【Linux/UNIX系统编程】文件I/O 2
items: 【Linux/UNIX系统编程】通用I/O模型
items: 【Linux/UNIX系统编程】 系统编程概念
items: 【深度学习】Spatical Transformer

【DPDK】响应ICMP echo请求
type: item
date: 2023-07-06T17:28:00+08:00
description: 相关代码：DPDK_Learning/ICMP_echo_reply at main · mfdycs/DPDK_Learning (github.com) ICMP报文格式如图所示，每一个ICMP消息都将包含引发这条ICMP消息的数据包的完全IP包头，ICMP报文则作为IP数据包的数据部分封装在IP数据包内部 Type字段表示ICMP消息的类型； Code字段表示ICMP消息类型细分的子类型； Checksum字段表示ICMP报文的校验和。 对于ICMP响应报文，各字段的具体内容如下： 添加标识 #define ENABLE_ICMP 1 主函数判断 主函数中判断是否为 8 0 echo请求报文，是的话返回响应 #if ENABLE_ICMP if (iphdr->next_proto_id == IPPROTO_ICMP) { struct rte_icmp_hdr *icmphdr = (struct rte_icmp_hdr *)(iphdr + 1); struct in_addr addr; addr.s_addr = iphdr->src_addr; printf("icmp ---> src: %s ", inet_ntoa(addr)); // 如果是ICMP请求: 8 0 if (icmphdr->icmp_type == RTE_IP_ICMP_ECHO_REQUEST) { addr.s_addr = iphdr->dst_addr; printf("icmp ---> local: %s , type : %d\n", inet_ntoa(addr), icmphdr->icmp_type); struct rte_mbuf *txbuf = ng_send_icmp(mbuf_pool, ethr->s_addr.addr_bytes, iphdr->dst_addr, iphdr->src_addr, icmphdr->icmp_ident, icmphdr->icmp_seq_nb); rte_eth_tx_burst(DPDK_PortID, 0, &txbuf, 1); rte_pktmbuf_free(txbuf); rte_pktmbuf_free(mbufs[i]); } } #endif 发送函数 回发的时候需要发送IP, 目的IP, ICMP是网络层协议, 没有端口的概念 此外还需要ICMP协议首部相关值，根据ICMP官方文档，响应时identifier字段为发送时的值 static struct rte_mbuf *ng_send_icmp(struct rte_mempool *mbuf_pool, uint8_t *dst_mac, uint32_t sip, uint32_t dip, uint16_t id, uint16_t seqnb) { const unsigned total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_icmp_hdr); struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool); if (!mbuf) { rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n"); } mbuf->pkt_len = total_length; mbuf->data_len = total_length; uint8_t *pkt_data = rte_pktmbuf_mtod(mbuf, uint8_t *); ng_encode_icmp_pkt(pkt_data, dst_mac, sip, dip, id, seqnb); return mbuf; } 封装ICMP数据 static int ng_encode_icmp_pkt(uint8_t *msg, uint8_t *dst_mac, uint32_t sip, uint32_t dip, uint16_t id, uint16_t seqnb) { // 1. ethhdr struct rte_ether_hdr* eth = (struct rte_ether_hdr*)msg; rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN); rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN); eth->ether_type = hton…

【DPDK】接收ARP请求并响应
type: item
date: 2023-06-29T17:03:00+08:00
description: 相关代码：DPDK_Learning/ARP_response at main · mfdycs/DPDK_Learning (github.com) 相关原理 ARP数据包 ARP Request ARP Response ARP的功能 1.响应其他机器的arp request（本章实现） 2.定时主动发送arp request，响应其他机器的arp reply，记录在arp table中 ARP是一个广播协议，不是一对一的通信 ARP操作 # 查看arp表 arp -a # 查看网卡对应的信息 netsh i i show in # 删除网卡的arp表 netsh i i delete neighbors 22 ## 此时再使用工具发送数据包，会发现找不到对应的arp映射 # 添加arp映射 netsh -c i i add neighbors 22 192.168.81.128 XX-XX-XX-XX-XX-XX # 此时发送数据正常 对于DPDK，响应ARP请求一共有三个步骤： 识别ARP请求 是否是自己的机器 发送ARP响应 添加标识 #define ENABLE_ARP 1 与UDP发送类似，构造ARP响应 #if ENABLE_ARP static int ng_encode_arp_pkt(uint8_t *msg, uint8_t *dst_mac, uint32_t sip, uint32_t dip) { // 1 ethhdr struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg; rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN); rte_memcpy(eth->d_addr.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN); eth->ether_type = htons(RTE_ETHER_TYPE_ARP); // 2 arp struct rte_arp_hdr *arp = (struct rte_arp_hdr *)(eth + 1); // eth首部下一位 arp->arp_hardware = htons(1); // 硬件类型 arp->arp_protocol = htons(RTE_ETHER_TYPE_IPV4); // 协议类型 arp->arp_hlen = RTE_ETHER_ADDR_LEN; // 硬件地址长度 arp->arp_plen = sizeof(uint32_t); // 协议地址长度: len(ipv4) arp->arp_opcode = htons(2); // 操作码 rte_memcpy(arp->arp_data.arp_sha.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN); // 发送者硬件地址 rte_memcpy( arp->arp_data.arp_tha.addr_bytes, dst_mac, RTE_ETHER_ADDR_LEN); // 接收者硬件地址 arp->arp_data.arp_sip = sip; arp->arp_data.arp_tip = dip; return 0; } static struct rte_mbuf* ng_send_arp(struct rte_mempool *mbuf_pool, uint8_t *dst_mac, uint32_t sip, uint32_t dip) { // len(arp:42) = len(eth:14) + len(arp:28) const unsigned total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_arp_hdr); struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool); if (!mbuf) { rte_exit(EXIT_FAILURE, "rte_pktmbuf_alloc\n"); } mbuf->pkt_len = total_length; mbuf->data_len = total_length; uint8_t *pkt_data = rte_pktmbuf_mtod(mbuf, uint8_t *); ng_encode_arp_pkt(pkt_data, dst_mac, sip, dip); return mbuf; } #endif 主函数解析数据并识别 #if ENABLE_ARP if (…

【DPDK】接收并转发UDP数据
type: item
date: 2023-06-22T16:53:00+08:00
description: 相关代码：DPDK_Learning/UDP_transmit at main · mfdycs/DPDK_Learning (github.com) DPDK启动逻辑框架可以参考 接收UDP数据 定义网络信息 #define ENABLE_SEND 1 #define ENABLE_ARP 1 // 发送数据需要的网络信息, 先不考虑传参 #if ENABLE_SEND static uint32_t gSrcIp; static uint32_t gDstIp; static uint8_t gSrcMac[RTE_ETHER_ADDR_LEN]; static uint8_t gDstMac[RTE_ETHER_ADDR_LEN]; static uint16_t gSrcPort; static uint16_t gDstPort; #endif 启动发送队列 // 配置网卡信息 const int num_tx_queues = 1; // 发送队列网卡数量 struct rte_eth_conf port_conf = port_conf_default; #if ENABLE_SEND // 启动tx队列 struct rte_eth_txconf txq_conf = dev_info.default_txconf; // 修改tx对应负载：把发送大小设置为接收大小 txq_conf.offloads = port_conf.rxmode.offloads; // rte_eth_tx_queue_setup(port_id, tx_queue_id, nb_rx_desc, socket_id, tx_conf); ret = rte_eth_tx_queue_setup(DPDK_PortID, 0, 128, rte_eth_dev_socket_id(DPDK_PortID), &txq_conf); if (ret < 0) { rte_exit(EXIT_FAILURE, "Could not setup TX queue\n"); } #endif 发送数据 从 mempool 里面获取到 mbuf，mbuf是内存池中的最小单元 UDP首部长度为42字节：14(eth) + 20(ip) + 8(udp) static struct rte_mbuf* ng_send(struct rte_mempool *mbuf_pool, unsigned char* data, uint16_t length) { struct rte_mbuf* mbuf = rte_pktmbuf_alloc(mbuf_pool); if (!mbuf) { rte_exit(EXIT_FAILURE, "Error with EAL init\n"); } mbuf->pkt_len = total_len; mbuf->data_len = total_len; uint8_t *pktdata = rte_pktmbuf_mtod(mbuf, uint8_t*); ng_encode_udp_pkt(pktdata, data, total_len); return mbuf; } 封装UDP数据 UDP数据头部信息： 以太网协议头： IP协议头： // encode udp pkt static int ng_encode_udp_pkt(uint8_t *msg, unsigned char* data, uint16_t total_len) { // 1. ethhdr struct rte_ether_hdr* eth = (struct rte_ether_hdr*)msg; rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN); rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN); eth->ether_type = htons(RTE_ETHER_TYPE_IPV4); // 2. iphdr struct rte_ipv4_hdr* ip = (struct rte_ipv4_hdr*)(msg + sizeof(struct rte_ether_hdr)); ip->version_ihl = 0x54; // 版本+首部长度信息, 网络字节序 ip->type_of_service = 0; ip->total_length = htons(total_len - sizeof(struct rte_ether_hdr)); ip->pack…

【DPDK】接收UDP数据
type: item
date: 2023-06-15T16:50:00+08:00
description: 相关代码：DPDK_Learning/UDP_recv at main · mfdycs/DPDK_Learning (github.com) 思路步骤 1. 进程初始化 固定方法，用于初始化DPDK，检测巨页、CPU是否有设置 if (rte_eal_init(argc, argv) < 0) { rte_exit(EXIT_FAILURE, "Error with EAL init\n"); } 2. 初始化内存池 一个进程确定一个内存池, 用于接收数据 struct rte_mempool* rte_pktmbuf_pool_create(const char* name, unsigned n, unsigned cache_size, uint16_t priv_size, uint16_t data_room_size, int socket_id); 参数： name: The name of the mbuf pool. n: The number of elements in the mbuf pool. The optimum size (in terms of memory usage) for a mempool is when n is a power of two minus one: n = (2^q - 1). cache_size: Size of the per-core object cache. See rte_mempool_create() for details. priv_size: Size of application private are between the rte_mbuf structure and the data buffer. This value must be aligned to RTE_MBUF_PRIV_ALIGN. data_room_size: Size of data buffer in each mbuf, including RTE_PKTMBUF_HEADROOM. socket_id: The socket identifier where the memory should be allocated. The value can be SOCKET_ID_ANY if there is no NUMA constraint for the reserved zone. 为了提升性能，我们不设置整4k，而是 4k-1，这样大块可以放在另外的地方 #define NUM_MBUFS (4096-1) struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbuf pool", NUM_BUFS, 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()); if (mbuf_pool == NULL) { rte_exit(EXIT_FAILURE, "Could not create mbuf pool\n"); } 3. 初始化网卡 确定传送/接收数据的网卡 在安装DPDK的时候，我们会先绑定相应的网卡，可以选择相对应的功能查看绑定信息 43-45插入，选择49-50可以绑定，48查看信息 Option: 43 Option: 44 Option: 45 Option: 48 最前面的为PCI地址 选择49，输入 0000:03:00.0 绑定 VMXNET3 多队列网卡 此时可能会提示该设备被占用，在使用前需要先 down 掉 ens 为虚拟网卡, 需要先改为 ethx 初始化网卡 #define NUM_BUFS (4096-1) int DPDK_PortID = 0; static const struct rte_eth_conf port_conf_default = { .rxmode = { .max_rx_pkt_len = RTE_ETHER_MAX_LEN, }, }; static void ng_init_port(struct rte_mempool *mbuf_pool) { uint16_t nb_sys_ports = rte_eth_dev_count_avail(); if (nb_sys_ports == 0) { rte_exit(EXIT_FAILURE, "No Supported eth found\n"); } #if 0 struct rte_eth_dev_info dev_info; rte_eth_dev_info_get(DPDK_PortID, &dev_info); # …

探坑记录：WSL2 搭建 Web 服务（有深坑）
type: item
date: 2022-09-20T16:27:00+08:00
description: 背景 最近本人尝试搭建几个局域网 Web 服务 考虑到wsl2有以下优势： 可以直接挂载本地硬盘：/mnt/c 可以根据程序需求动态变更所需资源 对外跟宿主机IP是一致的，本机可以通过 127.0.0.1 访问，外部设备可以通过宿主机IP访问 于是尝试采用WSL2搭建 Web 服务... 过程 在搭建好后，发现宿主机需要开启端口转发其他电脑才能访问到，网上教程如下： netsh interface portproxy add v4tov4 listenport=8001 listenaddress=0.0.0.0 connectport=8001 connectaddress=localhost 侦听 ipv4: 连接到 ipv4: 地址 端口 地址 端口 --------------- ---------- --------------- ---------- 0.0.0.0 8001 localhost 8001 此时一切正常，局域网其他主机可以顺利访问到 WSL2 中的 Web 服务，胜利仿佛在对我挥手 但在重启电脑后，出现以下bug： 其他设备无法访问到WSL2中的 Web 服务 宿主机 127.0.0.1:8001 无法访问 localhost:8001 可以访问 172.*.*.*:8001 可以访问 在其他设备和宿主机的 172.0.0.1 尝试连接时，宿主机CPU会跑满，根据相应端口和对应PID，查到此时iphlpsvc 进程会占满CPU，查看此进程为IP Helper 服务 尝试百度无解...GitHub issue区有相同情况WSL 2 Linux host cannot be accessed by a custom domain name or 127.0.0.1 · Issue #5728 · microsoft/WSL · GitHub 于是尝试debug： 查看宿主机Hosts，发现无异常 检查宿主机和 WSL2 的系统防火墙，发现无异常 重启 WSL2，无解 将Windows宿主机的网络重置：将所有网络适配器重置为出厂设置，无解 临时启动其他端口的 Web 服务，发现宿主机可以通过127.0.0.1:port进行连接，但之前设置过的端口依旧无法使用，此时考虑到是否是映射环境出现了问题 于是乎，在一位博主的帖子中(Troubleshooting WSL + localhost:3000 'connection refused' issue - DEV Community 👩‍💻👨‍💻)发现，关闭端口转发后，IP Helper 不会高占用，而且本机127.0.0.1:8001 可以正常访问web服务，但其他设备就无法访问到 WSL2 中的 Web 服务 思考：可能是相同端口转发出现了混乱？ 解决 自己尝试：将0.0.0.0:7001和 localhost:8001进行映射，重启服务，发现可用 netsh interface portproxy add v4tov4 listenport=7001 listenaddress=0.0.0.0 connectport=8001 connectaddress=localhost net stop iphlpsvc net start iphlpsvc 侦听 ipv4: 连接到 ipv4: 地址 端口 地址 端口 --------------- ---------- --------------- ---------- 0.0.0.0 7001 localhost 8001 或者：将宿主机的对外 IP:Port 和 localhost:8001进行映射，重启服务，发现可用（此处IP较为敏感，用 __.__.__.__ 代替） PS > netsh interface portproxy delete v4tov4 listenport=7001 listenaddress=0.0.0.0 PS > netsh interface portproxy add v4tov4 listenport=7001 listenaddress=__.__.__.__ connectport=8001 connectaddress=localhost PS > netsh interface portproxy show all 侦听 ipv4: 连接到 ipv4: 地址 端口 地址 端口 --------------- ---------- --------------- ---------- __.__.__.__ 7001 localhost 8001 PS > net stop iphlpsvc IP Helper 服务正在停止. IP Helper 服务已成功停止…

【Linux/UNIX系统编程】进程
type: item
date: 2022-08-01T17:37:00+08:00
description: 1. 进程号和父进程号 获取进程号： #include <unistd.h> pid_t getpid(void); // always successfully returns process ID of caller 可以通过 Linux系统特有的/proc/sys/kernel/pid_max 文件来进行查看或调整进程号上限（其值=最大进程号+1）。 一旦进程号达到上限，会将进程号计数器重置为 300，而不是 1。之所以如此，是因为低数值的进程号为系统进程和守护进程所长期占用，在此范围内搜索尚未使用的进程号只会是浪费时间。 父进程进程号： #include <unistd.h> pid_t getppid(void); // always successfully returns process ID of parent of caller 如果子进程的父进程终止，init 进程随即将收养该进程，子进程后续对 getppid () 的调用将返回进程号 1。 2. 进程内存分布 3. 虚拟内存管理 4. 栈和栈帧 5. 命令行参数 当执行程序时，命令行参数（command-line argument）（由 shell 逐一解析）通过两个入参提供给 main () 函数： 第一个参数 int argc，表示命令行参数的个数 第二个参数 char *argv[]，是一个指向命令行参数的指针数组，每一参数又都是以空字符（null）结尾的字符串。 第一个字符串，亦即 argv[0] 指向的，（通常）是该程序的名称 argv 中的指针列表以 NULL 指针结尾（即 argv[argc]为 NULL） 通过/proc/PID/cmdline文件可以读取任一进程的命令行参数；GNU C可使用program_invocation_name和program_invocation_short_name找到程序名称。 参数存储自己上限通过ARG_MAX限定，通过调用sysconf(__SC_ARG_MAC)确定上限值。 程序使用getopt () 库函数解析命令行选项。 6. 环境列表 可以使用全局变量 char **environ 访问环境列表，C运行时就定义并赋值了 #include "lib/tlpi_hdr.h" extern char **envirion; int main(int argc, char *argv[]) { char **ep; for (ep = envirion; *ep != NULL; ++ep) puts(*ep); exit(EXIT_SUCCESS); } 可以声明 main() 函数中的第三个参数来访问，但不推荐使用，该参数作用域仅在main () h函数内，而且该特性也不在规范中。 int main(int argc, char *argv[], char *envp[]) getenv () 函数能够从进程环境中检索单个值。 #include <stdlib.h> char *getenv(const char *name); // returns pointer to (vlaue) string, or NULL if no such variable 向 getenv () 函数提供环境变量名称，该函数将返回相应字符串指针。 如果指定 SHELL 为参数 name，那么将返回/bin/bash。 如果不存在指定名称的环境变量，那么 getenv () 函数将返回 NULL。 若需要改变一个环境变量的值，可以使用 setenv () 函数或 putenv () 函数 putenv () 函数向调用进程的环境中添加一个新变量，或者修改一个已经存在的变量值。 #include <stdlib.h> int putenv(char *string); // returns 0 on success, or nonzero on error 参数 string 是一指针，指向 name=value 形式的字符串。调用 putenv () 函数后，该字符串就成为环境的一部分，而不是复制副本，因此如果修改string变量，进程的环境也将被修改。 此外，string变量不应为自动变量，因为变量函数返回号，内存区域很有可能会被重写 setenv () 函数可以代替 putenv () 函数，向环境中添加一个变量。 #include <stdlib.h> int setenv(const char *name, const char *value, int overwrite); // returns 0 on success, or -1 on error setenv（）函数为形如name=value的字…

【Linux/UNIX系统编程】文件I/O 2
type: item
date: 2022-07-28T10:05:00+08:00
description: 1. 文件控制操作：fcntl() #include <fcntl.h> int fcntl(int fd, int cmd, ...); // return on success depends on cmd, or -1 on error 打开文件的状态标志 fcntl()的用途之一是针对一个打开的文件，获取或修改其访问模式和状态标志（这些值是通过指定 open()调用的 flag 参数来设置的）。要获取这些设置，应将 fcntl()的 cmd 参数设置为F_GETFL。 fcntl() 的 F_SETFL 命令来修改打开文件的某些状态标志。允许更改的标志有 O_APPEND、O_NONBLOCK、O_NOATIME、O_ASYNC 和 O_DIRECT。 使用 fcntl() 修改文件状态标志，尤其适用于如下场景: 文件不是由调用程序打开的，所以程序也无法使用 open() 调用来控制文件的状态标志 文件描述符的获取是通过 open() 之外的系统调用 比如 pipe() 调用，该调用创建一个管道，并返回两个文件描述符分别对应管道的两端 比如 socket() 调用，该调用创建一个套接字并返回指向该套接字的文件描述符 为了修改打开文件的状态标志，可以使用 fcntl () 的 F_GETFL 命令来获取当前标志的副本，然后修改需要变更的比特位，最后再次调用 fcntl () 函数的 F_SETFL 命令来更新此状态标志。因此，为了添加 O_APPEND 标志，可以编写如下代码： int flags; flags = fcntl(fd, F_GETFL); if (flags == -1) errExit("fcntl"); flags |= O_APPEND; if (fcntl(fd, F_SETFL, flags) == -1) errExit("fcntl"); 2. 文件描述符与文件 文件描述符和打开的文件并不是一一对应的关系，多个文件描述符可以指向同一个打开文件，这些文件描述符可以在相同或不同的进程中打开。 进程级的文件描述符表。每个进程有自己的打开文件的描述符表。其中每个条目都记录了单个文件描述符的相关信息，比如： 控制文件描述符操作的标志。这是文件描述符自己的属性。 对打开文件句柄的引用。 系统级的打开文件表。针对所有打开的文件，内核有一个系统级的描述表，其中每个条目称为打开文件句柄，存储了与一个打开文件相关的全部信息，比如： 当前文件偏移量（调用read和write时更新，或者使用lseek直接修改）。 打开文件时所使用的状态标志（open的flags参数）。 文件访问模式（如调用 open () 时所设置的只读模式、只写模式或读写模式）。 与信号驱动 I/O 相关的设置。 对该文件inode对象的引用。 inode表。文件系统，每个文件系统都会为其中所有文件建立一个inode表。其中条目为文件的inode信息，比如： 文件类型（例如，常规文件、套接字）和访问权限。 文件的各种属性，包括文件大小以及与不同类型操作相关的时间戳。 在进程 A 中，文件描述符 1 和 20 都指向同一个打开的文件句柄（标号为 23）。这可能是通过调用 dup ()、dup2 () 或 fcntl () 而形成的。 进程A的文件描述符2和进程B的文件描述符2都指向同一个打开的文件句柄（标号为73）。这种情形可能在调用 fork () 后出现（即，进程 A 与进程 B 之间是父子关系），或者当某进程通过UNIX 域套接字将一个打开的文件描述符传递给另一进程时，也会发生。 此外，进程 A 的描述符 0 和进程 B 的描述符 3 分别指向不同的打开文件句柄，但这些句柄均指向 i-node 表中的相同条目（1976），换言之，指向同一文件。发生这种情况是因为每个进程各自对同一文件发起了 open () 调用。同一个进程两次打开同一文件，也会发生类似情况。 总结如下： 两个不同的文件描述符，若指向同一打开文件句柄，将共享同一文件偏移量。因此，如果通过其中一个文件描述符来修改文件偏移量（由调用 read ()、write () 或 lseek () 所致），那么从另一文件描述符中也会观察到这一变化。无论这两个文件描述符分属于不同进程，还是同属于一个进程，情况都是如此。 要获取和修改打开的文件标志（例如，O_APPEND、O_NONBLOCK 和 O_ASYNC），可执行 fcntl () 的 F_GETFL 和 F_SETFL 操作，其对作用域的约束与上一条颇为类似。 相形之下，文件描述符标志（亦即，close-on-exec 标志）为进程和文件描述符所私有。对这一标志的修改将不会影响同一进程或不同进程中的其他文件描述符。 3. 复制文件描…

【Linux/UNIX系统编程】通用I/O模型
type: item
date: 2022-07-27T11:08:00+08:00
description: 本章重点介绍用于文件输入/输出的系统调用。本章开篇会讨论文件描述符的概念，随后会逐一讲解构成通用 I/O 模型的系统调用，其中包括：打开文件、关闭文件、从文件中读数据和向文件中写数据。 所有执行 I/O 操作的系统调用都以文件描述符，一个非负整数（通常是小整数），来指代打开的文件。 文件描述符用以表示所有类型的已打开文件，包括管道（pipe）、FIFO、socket、终端、设备和普通文件。 文件描述符 用途 POSIX 名称 stdio 流 0 标准输入 STDIN_FILENO stdin 1 标准输出 STDOUT_FILENO stdout 2 标准错误 STDERR_FILENO stderr 在程序开始运行之前，shell 代表程序打开这 3 个文件描述符。更确切地说，程序继承了 shell 文件描述符的副本—在 shell 的日常操作中，这 3 个文件描述符始终是打开的。 针对 stdout 调用 freopen () 函数后，无法保证 stdout 变量值仍然为 1 执行文件I/O操作的4个主要系统调用： fd = open(pathname, flags, mode) 打开 pathname 所标识的文件，并返回文件描述符，用以在后续函数调用中指代打开的文件 如果文件不存在，open () 函数可以创建之，这取决于对位掩码参数 flags 的设置 flags 参数可指定文件的打开方式：只读、只写亦或是读写方式 mode 参数则指定了由 open () 调用创建文件的访问权限 numread = read(fd, buffer, count) 调用从 fd 所指代的打开文件中读取至多 count 字节的数据，并存储到 buffer 中 read () 调用的返回值为实际读取到的字节数，无字节可读返回0 numwritten = write(fd, buffer, count) 调用从 buffer 中读取多达 count 字节的数据写入由fd 所指代的已打开文件中 status = close(fd) 释放文件描述符 fd 以及与之相关的内核资源 例子：实现拷贝文件操作 ./copy oldfile newfile #include<sys/stat.h> #include<fcntl.h> #include"lib/tlpi_hdr.h" #ifndef BUF_SIZE // allow "cc -D" to override #define BUF_SIEZ 1024 #endif int main(int argc, char *argv[]) { int inputFd, outputFd, openFlags; mode_t filePerms; ssize_t numRead; char buf[BUF_SIEZ]; if (argc != 3 || strcmp(argv[1], "--help") == 0) usageErr("%s old-file new-file\n", argv[0]); // open input and output files inputFd = open(argv[1], O_RDONLY); if (inputFd == -1) errExit("opening file %s", argv[1]); openFlags = O_CREAT | O_WRONLY | O_TRUNC; filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; // rw-rw-rw outputFd = open(argv[2], openFlags, filePerms); if (outputFd == -1) errExit("opening file %s", argv[2]); // transfer data while ((numRead = (read(inputFd, buf, BUF_SIEZ)) > 0)) { if (write(outputFd, buf, numRead) != numRead) fatal("couldn't write whole buffer"); } if (numRead == -1) errExit("read"); if (close(inputFd) == -1) errExit("close input"); if (close(outputFd) == -1) errExit("close output"); exit(EXIT_SUCCESS); } 1. 打开文件：open () #include<sys/stat.h> #in…

【Linux/UNIX系统编程】 系统编程概念
type: item
date: 2022-07-26T17:00:00+08:00
description: 1. 系统调用 以x86平台为例，分析系统调用： 应用程序通过调用 C 语言函数库中的外壳（wrapper）函数，来发起系统调用 对系统调用中断处理例程来说，外壳函数必须保证所有的系统调用参数可用。通过堆栈，这些参数传入外壳函数，但内核却希望将这些参数置入特定寄存器。因此，外壳函数会将上述参数复制到寄存器。 由于所有系统调用进入内核的方式相同，内核需要设法区分每个系统调用。为此，外壳函数会将系统调用编号复制到一个特殊的 CPU 寄存器（%eax）中。 外壳函数执行一条中断机器指令（int 0x80），引发处理器从用户态切换到核心态，并执行系统中断 0x80 (十进制数 128) 的中断矢量所指向的代码。 较新的 x86-32 硬件平台实现了 sysenter 指令，较之传统的 int 0x80 中断指令，sysenter指令进入内核的速度更快。2.6 内核及 glibc 2.3.2 以后的版本都支持 sysenter 指令。 为响应中断 0x80，内核会调用 system_call () 例程（位于汇编文件 arch/i386/entry.S 中）来处理这次中断，具体如下： 在内核栈中保存寄存器值。 审核系统调用编号的有效性。 以系统调用编号对存放所有调用服务例程的列表（内核变量 sys_call_table）进行索引，发现并调用相应的系统调用服务例程。若系统调用服务例程带有参数，那么将首先检查参数的有效性。例如，会检查地址指向用户空间的内存位置是否有效。随后，该服务例程会执行必要的任务，这可能涉及对特定参数中指定地址处的值进行修改，以及在用户内存和内核内存间传递数据（比如，在 I/O 操作中）。最后，该服务例程会将结果状态返回给 system_call () 例程。 从内核栈中恢复各寄存器值，并将系统调用返回值置于栈中。 返回至外壳函数，同时将处理器切换回用户态。 若系统调用服务例程的返回值表明调用有误，外壳函数会使用该值来设置全局变量 errno。然后，外壳函数会返回到调用程序，并同时返回一个整型值，以表明系统调用是否成功。 在 Linux 上，系统调用服务例程遵循的惯例是调用成功则返回非负值。发生错误时，例程会对相应 errno 常量取反，返回一负值。C 语言函数库的外壳函数随即对其再次取反（负负得正），将结果拷贝至 errno，同时以-1 作为外壳函数的返回值返回，向调用程序表明有错误发生。 上述惯例所依赖的前提条件是系统调用服务例程，若调用成功则不会返回负值。可是，对于少数例程来说，这一前提并不成立。一般情况下，这也不会有问题，因为取反的 errno值范围不会与调用成功返回负值的范围有交集。不过，有一种情况，沿用这个惯例确实会出问题：系统调用 fcntl () 的 F_GETOWN 操作。 2. 处理来自系统调用和库函数的错误 少数几个系统函数在调用时从不失败。例如，getpid() 总能成功返回进程的 ID，而 _exit() 总能终止进程。无需对此类系统调用的返回值进行检查。 处理系统调用错误 每个系统调用的手册页记录有调用可能的返回值，并指出了哪些值表示错误。通常，返回值为-1 表示出错。因此，可使用下列代码对系统调用进行检查： // system call to open a file fd = open(pathname, flags, mode); if (fd == -1) { // Code to handle the error } // ... if (close(fd) == -1) { // Code to handle the error } 系统调用失败时，会将全局整形变量 errno 设置为一个正值，以标识具体的错误。如果调用系统调用和库函数成功，errno 绝不会被重置为 0，故此，该变量值不为 0，可能是之前调用失败造成的。因此，在进行错误检查时，必须坚持首先检查函数的返回值是否表明调用出错，然后再检查 errno 确定错误原因。 少数系统调用（比如，getpriority ()）在调用成功后，也会返回−1。 系统调用失败后，常见的做法之一是根据 errno 值打印错误消息。提供库函数 perror() 和 strerror ()，就是出于这一目的。 函数 perror() 会打印出其 msg 参数所指向的字符串，紧跟一条与当前 errno 值相对应的消息。 函数 strerror() 会针对其 errnum 参数中所给定的错误号，返回相应的错误字符串。 #include<stdio.h> void perror(const char *msg); #include<string.h> char *strerror(int errnum); 例如： fd = open(pathnam…

【深度学习】Spatical Transformer
type: item
date: 2022-05-27T21:59:00+08:00
description: 出现原因：CNN不能解决图像放大缩小和旋转的问题（即数据增强问题） Spatial Transformer Layer是在CNN前又叠了一个Neuron Network 它不仅可以transform input layer，也可以被放在CNN里面，transform feature map 那么如何寻找weight呢？ 放大缩小： 旋转： 如果只旋转、平移、缩放的话，只需要六个参数 例子如下： 但我们不能直接设为最近的点，因为无法微分，gradient为0，所以我们使用双线性插值，这样就构成连续函数