其实这就涉及到了unix下从configure、make、make install一系列的步骤。
一般来说,拿到source packet后,我们首先运行./configure命令
./configure --prefix=/usr/local --enable-xx --with=yy
自己安装的软件,最后安装在/usr/local目录下,这样能够减少很多麻烦。好像,其他的软件如果需要其他的共享库,默认去/usr/local中查找,如果找不到,就会报链接错误。
Here is a task for tutorial_bcc_python_developer,how to deal with ctrl +c
.From stackoverflow,the answer will be performaced:
from bcc import BPF
import signal
import sys
def bpf():
BPF(text='''
int kprobe__sys_sync(void *ctx)
{ bpf_trace_printk("Hello, yubo\\n");
return 0;}'''
).trace_print()
def signal_handler(signal, frame):
print('You pressed Ctrl+c!')
bpf()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print('press Ctrl+c')
signal.pause()
Here, maybe some issue exist.But i thought, the model will be the same as c
在unix中,一个shell的执行流程如下:
读入命令
时间轴
------------------------>>
___ ____
sh----| |-------------------| |
^ |__| |___|
| | 新进程 |
| |____>"ls"--->退出 |____>"ps"
"ls"
使用execvp,这个函数有两个参数:要运行的程序名称和这个程序的命令行参数组。当程序运行时命令行参数以argv[]的形式传递给程序. 注意,将数组的第一个元素设置为程序的名称,最后一个元素必须是Null.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0;
printf("***中断这个程序的执行***\n");
execvp("ls", arglist);
printf("ls is done, bye\n");
}
yubo@debian:~/test/tmp/unix/shell$ ./exec
***中断这个程序的执行***
总用量 88
-rwxr-xr-x 1 yubo yubo 6108 5月 18 06:40 exec
-rw-r--r-- 1 yubo yubo 407 5月 18 07:08 exec1.c
-rw-r--r-- 1 yubo yubo 801 5月 16 19:28 execute.c
-rw-r--r-- 1 yubo yubo 29520 5月 16 19:05 segfault
-rwxr-xr-x 1 yubo yubo 13680 5月 16 19:30 smsh
-rw-r--r-- 1 yubo yubo 811 5月 16 19:30 smsh1.c
-rw-r--r-- 1 yubo yubo 389 5月 16 19:09 smsh.h
-rw-r--r-- 1 yubo yubo 2630 5月 16 19:30 splitline.c
-rwxr-xr-x 1 yubo yubo 6188 5月 16 19:49 test
-rw-r--r-- 1 yubo yubo 265 5月 16 19:48 test.c
这里你有没有注意到上面程序中的第二条printf语句消息了。这里书上的解释是:内核将新进程载入到当前进程,替换了当前进程的代码和数据,也就是说,execvp这个函数将“ls”这个命令加入到当前的程序,代替了后面的语句,从而使后面的printf语句消失掉。
下面的代码更深入一步:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#define MAXARGS 20 /* cmdline args */
#define ARGLEN 100 /* token length */
int execute(char *const arglist[])
{
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
}
char *makestring(char *buf)
{
char *cp;
buf[strlen(buf) - 1] = '\0';
cp = malloc(strlen(buf) + 1);
if (cp == NULL) {
fprintf(stderr, "no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
int main()
{
char *arglist[MAXARGS + 1];
int numargs;
char argbuf[ARGLEN]; /* read stuff from here */
numargs = 0;
while( numargs < MAXARGS) {
printf("Arg[%d]?", numargs);
if (fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n') {
arglist[numargs++] = makestring(argbuf);
}
else {
if (numargs > 0) {
printf("%s", argbuf);
arglist[numargs] = NULL;
if (execute(arglist))
numargs = 0;
}
}
}
return 0;
}
下面是演示:
yubo@debian:~/test/tmp/unix/shell$ ./psh
Arg[0]?ls
Arg[1]?-l
Arg[2]?.
Arg[3]?
总用量 28
-rwxr-xr-x 1 yubo yubo 6108 5月 18 18:01 exec
-rw-r--r-- 1 yubo yubo 413 5月 18 18:01 exec1.c
-rwxr-xr-x 1 yubo yubo 9012 5月 18 19:17 psh
-rw-r--r-- 1 yubo yubo 1116 5月 18 19:17 psh1.c
yubo@debian:~/test/tmp/unix/shell$ ls
exec exec1.c psh psh1.c
这个代码,条理还是比较清楚的。
上面的程序只可运行一次,如果想继续使用,需要再一次运行程序。那么,如何使用真正的shell呢?
答案是fork().
在这篇文章里,我们详细讲解了fork的用法。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define MAXARGS 20
#define ARGLEN 100
void execute(char **); /* execvp */
char *makestring(char *); /* to restore args vector */
int main()
{
char *arglist[MAXARGS + 1];
int numargs = 0;
char argbuf[ARGLEN];
while(numargs < MAXARGS){
printf("Arg[%d]", numargs);
if (fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n')
arglist[numargs++] = makestring(argbuf);
else {
if (numargs > 0) {
arglist[numargs] = NULL;
execute(arglist);
numargs = 0;
}
}
}
return 0;
}
void execute(char *arglist[])
{
int pid, existstatus;
pid = fork(); /* make new process */
switch(pid){
case -1:
perror("fork failed");
exit(1);
case 0:
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
default:
while(wait(&existstatus) != pid)
;
printf("child exited with status %d, %d\n",
existstatus >> 8, existstatus &0377);
}
}
char *makestring(char *buf)
{
char *cp;
buf[strlen(buf) - 1] = '\0';
cp = malloc(strlen(buf) + 1);
if (cp == NULL){
fprintf(stderr, "no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
下面是输出结果:
yubo@debian:~/test/tmp/unix/shell$ ./psh
Arg[0]ls
Arg[1]-l
Arg[2].
Arg[3]
总用量 24
-rwxr-xr-x 1 yubo yubo 6108 5月 18 18:01 exec
-rwxr-xr-x 1 yubo yubo 9440 5月 18 23:00 psh
-rw-r--r-- 1 yubo yubo 1336 5月 18 23:00 psh2.c
child exited with status 0, 0
Arg[0]ps
Arg[1]
PID TTY TIME CMD
3393 pts/1 00:00:00 bash
3940 pts/1 00:00:00 psh
3944 pts/1 00:00:00 ps
child exited with status 0, 0
Arg[0]
但是,有一点是比较遗憾的:
./psh
Arg[0]tr
Arg[1][a-z]
Arg[2][A-Z]
Arg[3]
hello
HELLO
now to press
NOW TO PRESS
^C
yubo@debian:~/test/tmp/unix/shell$
针对tr程序(psh的子进程)的Ctrl+C将psh也给停止了,正确的做法是不影响父进程。
ICMP是layer 4协议,用户可以使用icmp来得到自己想要的东西。大名鼎鼎的ping就是利用ICMP的api写成的。
这个协议主要是用来处理(L3)层的错误信息与控制信息的,
这里主要是两类: 错误报文和消息报文。著名的ip(来自于iputils包)就是打开 raw socket并且发送一个ICMP_ECHO报文、产生ICMP_REPLY报文作为回应。
首先,在/net/ipv4/icmp.c中首先呼叫inet_init()(在boot阶段),方法,此方法激活icmp_init()方法,然后就会呼叫icmp_sk_init()产生ICMP包。
在 net/ipv4/af_inet.c 中有各种协议的注册。
static int __init inet_init(void)
{
struct inet_protosw *q;
struct list_head *r;
int rc = -EINVAL;
sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
rc = proto_register(&tcp_prot, 1);
if (rc)
goto out;
rc = proto_register(&udp_prot, 1);
if (rc)
goto out_unregister_tcp_proto;
rc = proto_register(&raw_prot, 1);
if (rc)
goto out_unregister_udp_proto;
/*
* Tell SOCKET that we are alive...
*/
(void)sock_register(&inet_family_ops);
#ifdef CONFIG_SYSCTL
ip_static_sysctl_init();
#endif
/*
* Add all the base protocols.
*/
if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
pr_crit("%s: Cannot add ICMP protocol\n", __func__);
if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
pr_crit("%s: Cannot add UDP protocol\n", __func__);
if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
pr_crit("%s: Cannot add TCP protocol\n", __func__);
#ifdef CONFIG_IP_MULTICAST
if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
pr_crit("%s: Cannot add IGMP protocol\n", __func__);
#endif
/* Register the socket-side information for inet_create. */
for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
INIT_LIST_HEAD(r);
for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
inet_register_protosw(q);
/*
* Set the ARP module up
*/
------->-------
接下来看看 icmp_protocal的结构
static const struct net_protocol icmp_protocol = {
.handler = icmp_rcv,
.err_handler = icmp_err,
.no_policy = 1,
.netns_ok = 1,
};
首先看看handler, 当接受到由外面发来的包,只要在ip头部等于IPPROTO_ICMP(0x1), icmp_rcv就会触发。
当设置为1,意味着不需要表现IPsec策略检查(policy check),
设置为1时提醒协议是network命名空间
icmp_sk_init() 位于net/ipv4/icmp.c 中
static int __net_init icmp_sk_init(struct net *net)
{
int i, err;
net->ipv4.icmp_sk = alloc_percpu(struct sock *);
if (!net->ipv4.icmp_sk)
return -ENOMEM;
for_each_possible_cpu(i) {
struct sock *sk;
err = inet_ctl_sock_create(&sk, PF_INET,
SOCK_RAW, IPPROTO_ICMP, net);
if (err < 0)
goto fail;
*per_cpu_ptr(net->ipv4.icmp_sk, i) = sk;
/* Enough space for 2 64K ICMP packets, including
* sk_buff/skb_shared_info struct overhead.
*/
sk->sk_sndbuf = 2 * SKB_TRUESIZE(64 * 1024);
/*
* Speedup sock_wfree()
*/
sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
inet_sk(sk)->pmtudisc = IP_PMTUDISC_DONT;
}
/* Control parameters for ECHO replies. */
net->ipv4.sysctl_icmp_echo_ignore_all = 0;
net->ipv4.sysctl_icmp_echo_ignore_broadcasts = 1;
/* Control parameter - ignore bogus broadcast responses? */
net->ipv4.sysctl_icmp_ignore_bogus_error_responses = 1;
/*
* Configurable global rate limit.
*
* ratelimit defines tokens/packet consumed for dst->rate_token
* bucket ratemask defines which icmp types are ratelimited by
* setting it's bit position.
*
* default:
* dest unreachable (3), source quench (4),
* time exceeded (11), parameter problem (12)
*/
net->ipv4.sysctl_icmp_ratelimit = 1 * HZ;
net->ipv4.sysctl_icmp_ratemask = 0x1818;
net->ipv4.sysctl_icmp_errors_use_inbound_ifaddr = 0;
return 0;
主要参考下面的这幅图片就可以了:
前64 bits是属于头文件的部分,后面的Payload包括原始包IPv4头文件和它的的负载。
头部的定义在include/uapi/linux/icmp.h
struct icmphdr {
__u8 type;
__u8 code;
__sum16 checksum;
union {
struct {
__be16 id;
__be16 sequence;
} echo;
__be32 gateway;
struct {
__be16 __unused;
__be16 mtu;
} frag;
__u8 reserved[4];
} un;
};
通过这张表,是不是可以得出这样一个结论:在网络协议中,它的bits size可以通过它的的类型的大小推断出来。
然后ICMPv4模块定义了icmp_control对象, 命名为 icmp_pointer,这个对象被ICMpv4消息的类型索引。
该定义位于 include/uapi/linux/icmp.c
/*
* ICMP control array. This specifies what to do with each ICMP.
*/
struct icmp_control {
bool (*handler)(struct sk_buff *skb);
short error; /* This ICMP is classed as an error message */
};
static const struct icmp_control icmp_pointers[NR_ICMP_TYPES+1];
其中, error是1,代表着错误消息(ICMP_DEST_UNREACH), 为0代表着正常消息(ICMP_ECHO)。 而handler有时会被赋值超过一种类型。
ping_rcv()接受一个ping的回应(ICMP_ECHOREPLY)。 位于net/ipv4/icmp.c
bool ping_rcv(struct sk_buff *skb)
{
struct sock *sk;
struct net *net = dev_net(skb->dev);
struct icmphdr *icmph = icmp_hdr(skb);
/* We assume the packet has already been checked by icmp_rcv */
pr_debug("ping_rcv(skb=%p,id=%04x,seq=%04x)\n",
skb, ntohs(icmph->un.echo.id), ntohs(icmph->un.echo.sequence));
/* Push ICMP header back */
skb_push(skb, skb->data - (u8 *)icmph);
sk = ping_lookup(net, skb, ntohs(icmph->un.echo.id));
if (sk) {
struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
pr_debug("rcv on socket %p\n", sk);
if (skb2)
ping_queue_rcv_skb(sk, skb2);
sock_put(sk);
return true;
}
pr_debug("no socket, dropping\n");
return false;
}
EXPORT_SYMBOL_GPL(ping_rcv);
在3.0以前的内核版本中,你需要在用户空间程序中创建一个raw socket,当接收到一个(ICMP_ECHOREPLY)消息, 原来的raw socket处理它(理解不准确)。为了详细理解这个过程,先看一个函数。
在 /net/ipv4/in_input.c中。
static int ip_local_deliver_finish(struct net *net,
struct sock *sk, struct sk_buff *skb)
{
__skb_pull(skb, skb_network_header_len(skb));
rcu_read_lock();
{
int protocol = ip_hdr(skb)->protocol;
const struct net_protocol *ipprot;
int raw;
resubmit:
raw = raw_local_deliver(skb, protocol);
ipprot = rcu_dereference(inet_protos[protocol]);
if (ipprot) {
int ret;
if (!ipprot->no_policy) {
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
goto out;
}
nf_reset(skb);
}
ret = ipprot->handler(skb);
if (ret < 0) {
protocol = -ret;
goto resubmit;
}
__IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
} else {
if (!raw) {
if (xfrm4_policy_check(NULL,
XFRM_POLICY_IN, skb)) {
__IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS);
icmp_send(skb, ICMP_DEST_UNREACH,
ICMP_PROT_UNREACH, 0);
}
kfree_skb(skb);
} else {
__IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
consume_skb(skb);
}
}
}
out:
rcu_read_unlock();
return 0;
}
如果你有用户空间的程序去测试,请参考这篇文章1
在net/下,有af_netlink.c af_netlink.h genetlink.c和diag.c
af_netlink 文件中提供了绝大多数的API,genetlink提供了新的API,这使得更容易创建netlink消息。diag.c是为了dump netlink消息,里面是相关的API。
netlink较其他内核与用户空间的交流方式具有以下优势:
不需要poll模式,前文我们说了。一个用户空间的程序打开一个socket,然后直接recvmsg(),如果没有从内核发送过来信息,就进入一个阻塞状态。例如,iproute2包中/lib/libnetlink.c中的 rtnl_listen()方法。还有一个优势是可以允许内核发送异步消息到用户空间,这不需要用户空间的程序去特定的抓取。最后一个优势就是netlink支持多播传输机制。
同样,创建netlink嵌套字使用socket()方法,类型可以是SOCK_RAW或者SOCK_DGRAM.
netlink嵌套字既可以在用户空间创建,也可以在内核空间创建。内核里使用方法netlink_kernel_create()去创建,这两者都会产生一个netlink_sock对象。用户空间的会被netlink_create()方法处理。内核里是__netlink_kernel_create(),这个方法设置了NETLINK_KERNEL_SOCKET_标志。最终所有的方法会调用__netlink_create()(源头其实是sk_alloc()),
用户空间的程序:
socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
然后创造一个sockaddr_nl的对象实例
libnl是与内核空间交流的用户空间程序的API,iproute2就是使用的libnl库,一般在这个源代码包的下面,有基本核心库(libnl),它支持一般netlink家族(libnl-genl),路由家族(libnl-route)和netfilter家族(libnl-nf).还有一个最小化的用户空间的库函数叫libmnl.
相关的源代码在这里here,下面就是结构图:
进行开发的文档在这里
先来一段用户空间的程序,看看大体概貌
#include <netlink/netlink.h>
#include <errno.h>
int main(int argc, char *argv[])
{
struct nl_sock *h[1025];
int i;
h[0] = nl_socket_alloc();
printf("Created handle with port 0x%x\n",
nl_socket_get_local_port(h[0]));
nl_socket_free(h[0]);
h[0] = nl_socket_alloc();
printf("Created handle with port 0x%x\n",
nl_socket_get_local_port(h[0]));
nl_socket_free(h[0]);
for (i = 0; i < 1025; i++) {
h[i] = nl_socket_alloc();
if (h[i] == NULL)
nl_perror(ENOMEM, "Unable to allocate socket");
else
printf("Created handle with port 0x%x\n",
nl_socket_get_local_port(h[i]));
}
return 0;
}
这是在编译的时候,有些麻烦,
gcc program.c $(pkg-config --cflags --libs libnl-3.0)
这里涉及了pkg-config的用法。这个工具还是非常有用的,他解决了有关已安装的库函数中的种种使用的问题。比如,库函数的版本、链接位置…pkg-config的具体以后补充。
struct nlmsghdr{
__u32 nlmsg_len;
__u32 nlmsg_type;
__u32 nlmsg_flags;
__u32 nlmsg_seq;
__u32 nlmsg_pid;
}
from <linux/uapi/linux/netlink.h>.这里重点介绍几个。
nlmsg_len
is the length of the message including the header
nlmsg_type
four basic netlink message header types:
NLMSG_NOOP : no operations, message must be discarded
NLMSG_ERROR: Error occured
NLMSG_DONE: A multipart message is terminated
NLMSG_OVERRUN: error, data was lost
nlmsg_flags
:
NLM_F_REQUEST - Message is a request, see Message Types.
NLM_F_MULTI - Multipart message, see Multipart Messages
NLM_F_ACK - ACK message requested, see ACKs.
NLM_F_ECHO - Request to echo the request.
NLM_F_ROOT - Return based on root of tree.
NLM_F_MATCH - Return all matching entries.
NLM_F_ATOMIC - Obsoleted, once used to request an atomic operation.
NLM_F_DUMP - Return a list of all objects (NLM_F_ROOT|NLM_F_MATCH).
NLM_F_REPLACE - Replace an existing object if it exists.
NLM_F_EXCL - Do not update object if it exists already.
NLM_F_CREATE - Create object if it does not exist yet.
NLM_F_APPEND - Add object at end of list.
nlmsg_seq
: the message sequence number
nlmsg_pid
: is the sending port id
看一下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 */
};
上面的成员参数可以看注释,这里说一下nl_pid,它是netlink socket的单播地址,对于内核来说,它应该是0;对于用户空间来说,应该是运行它的的pid(使用getpid()).
sock_diag就是提供了基于netlink子系统、可以得到有关socket的信息。有关网络的工具ss就是大量使用了sock_diag.