目前在学习研究linux系统,作为一名初学者,感觉很吃力,通过网友指点和自己琢磨,总结出,首先理解linux关键数据结构,对理解linux系统大有裨益,故总结下文——详述相关关键数据结构。
1. 前言
struct list_head *next, *prev;
};
struct list_head name = LIST_HEAD_INIT(name)
(ptr)->next = (ptr); (ptr)->prev = (ptr); /
} while (0)
void list_add(struct list_head *new, struct list_head *head);
void list_add_tail(struct list_head *new, struct list_head *head);
void list_del(struct list_head *entry);
void list_move(struct list_head *list, struct list_head *head);
void list_move_tail(struct list_head *list,struct list_head *head);
int list_empty(struct list_head *head);
void list_splice(struct list_head *list, struct list_head *head);
#define list_entry(ptr, type, member) /
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
#define list_for_each(pos, head) /
for (pos = (head)->next, prefetch(pos->next); pos != (head); /
pos = pos->next, prefetch(pos->next))
#define list_for_each_prev(pos, head) /
for (pos = (head)->prev, prefetch(pos->prev); pos != (head); /
pos = pos->prev, prefetch(pos->prev))
struct list_head list;
int data;
};
{
struct my_list *p;
int i;
for(i=0; i<100; i++){
p=kmalloc(sizeof(struct my_list), GFP_KERNEL);
list_add(&p->list, &mylist);
}
}
在内存中形成如下结构的一个双向链表:
| |
| mylist 99 98 0 |
| +----+ +---------+ +---------+ +---------+ |
+->|next|--->|list.next|--->|list.next|--->...--->|list.next|---+
|----| |---------| |---------| |---------|
+--|prev|<---|list.prev|<---|list.prev|<---...<---|list.prev|<--+
| +----+ |---------| |---------| |---------| |
| | data | | data | | data | |
| +---------+ +---------+ +---------+ |
| |
+---------------------------------------------------------------+
{
list_del(&p->list, &mylist);
kfree(p);
}
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
member是链表元素结构(如struct my_list)中链表头结构list_head参数的名称
type是链表元素结构类型(如struct my_list)
{
struct list_head *cur;
struct my_list *p;
p=list_entry(cur, struct my_list, list);
printk("data=%d/n", p->data);
}
}
1) 链表头中元素置为NULL不是初始化,与普通习惯不同;
2) 仍然需要单独编写各自的删除整个链表的函数,不能统一处理,因为不能保证所有链表元素结构中链表头结构list_head的偏移地址都是相同的,当然如果把链表头结构list_head都作为链表元素结构的第一个参数,就可以用统一的删除整个链表的函数。
3. HASH表
hash_conntrack(const struct ip_conntrack_tuple *tuple)
{
#if 0
dump_tuple(tuple);
#endif
return (jhash_3words(tuple->src.ip,
(tuple->dst.ip ^ tuple->dst.protonum),
(tuple->src.u.all | (tuple->dst.u.all << 16)),
ip_conntrack_hash_rnd) % ip_conntrack_htable_size);
}
static inline u32 jhash_3words(u32 a, u32 b, u32 c, u32 initval)
{
a += JHASH_GOLDEN_RATIO;
b += JHASH_GOLDEN_RATIO;
c += initval;
}
struct timer_list {
struct list_head list;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long);
};
expires:到期时间
function:到期函数,时间到期时调用的函数
data:传给到期函数的数据,实际应用中通常是一个指针转化而来,该指针指向一个结构
timer的操作:
extern void add_timer(struct timer_list * timer);
extern int del_timer(struct timer_list * timer);
(del_timer()函数可能会失败,这是因为该timer本来已经不在系统timer链表中了,也就是已经删除过了)
extern int del_timer_sync(struct timer_list * timer);
int mod_timer(struct timer_list *timer, unsigned long expires);
struct timer_list通常作为数据结构中的一个参数,在初始化结构的时候初始化timer,表示到期时要进行的操作,实现定时动作,通常更多的是作为超时 处理的,timer函数作为超时时的资源释放函数。注意:如果超时了运行超时函数,此时系统是处在时钟中断的bottom half里的,不能进行很复杂的操作,如果要完成一些复杂操作,如到期后的数据发送,不能直接在到期函数中处理,而是应该在到期函数中发个信号给特定内核 线程转到top half进行处理。
#define time_before(a,b) time_after(b,a)
#define time_before_eq(a,b) time_after_eq(b,a)
arg:线程主函数的参数;
flags:建立线程的标志;
{
static struct completion startup __initdata = COMPLETION_INITIALIZER(startup);
wait_for_completion(&startup);
return 0;
}
{
struct task_struct *curtask = current;
DECLARE_WAITQUEUE(wait, curtask);
struct k_sigaction sa;
strcpy(curtask->comm, "keventd");
keventd_running = 1;
keventd_task = curtask;
siginitsetinv(&curtask->blocked, sigmask(SIGCHLD));
recalc_sigpending(curtask);
spin_unlock_irq(&curtask->sigmask_lock);
sa.sa.sa_handler = SIG_IGN;
sa.sa.sa_flags = 0;
siginitset(&sa.sa.sa_mask, sigmask(SIGCHLD));
do_sigaction(SIGCHLD, &sa, (struct k_sigaction *)0);
* If one of the functions on a task queue re-adds itself
* to the task queue we call schedule() in state TASK_RUNNING
*/
for (;;) {
set_task_state(curtask, TASK_INTERRUPTIBLE);
add_wait_queue(&context_task_wq, &wait);
if (TQ_ACTIVE(tq_context))
set_task_state(curtask, TASK_RUNNING);
schedule();
remove_wait_queue(&context_task_wq, &wait);
run_task_queue(&tq_context);
wake_up(&context_task_done);
if (signal_pending(curtask)) {
while (waitpid(-1, (unsigned int *)0, __WALL|WNOHANG) > 0)
;
spin_lock_irq(&curtask->sigmask_lock);
flush_signals(curtask);
recalc_sigpending(curtask);
spin_unlock_irq(&curtask->sigmask_lock);
}
}
}
struct my_struct{
int a;
int b;
}c;
...
}
我们从一个实际的数据包发送的例子入手,来看看其发送的具体流程,以及过程中涉及到的相关数据结构。在我们的虚拟机上发送icmp回显请求包,ping另一台主机172.16.48.1。我们使用系统调用sendto发送这个icmp包。
ssize_t sendto(int s, const void *buf, size_t len, int flags,
const struct sockaddr *to, socklen_t tolen);
系统调用sendto最终调用内核函数asmlinkage long sys_sendto(int fd, void __user * buff, size_t len,unsigned flags, struct sockaddr __user *addr, int addr_len)
sys_sendto构建一个结构体struct msghdr,用于接收来自应用层的数据包,下面是结构体struct msghdr的定义:
struct msghdr {
void *msg_name;//存数据包的目的地址,网络包指向sockaddr_in
//向内核发数据时,指向sockaddr_nl
int msg_namelen;//地址长度
struct iovec *msg_iov;
__kernel_size_t msg_iovlen;
void *msg_control;
__kernel_size_t msg_controllen;
unsigned msg_flags;
};
这个结构体的内容可以分为四组。
第一组是msg_name和msg_namelen,记录这个消息的名字,其实就是数据包的目的地址。msg_name是指向一个结构体struct sockaddr的指针。长度为16:
struct sockaddr{
sa_family_t sa_family;
char sa_addr[14];
}
所以,msg_namelen的长度为16。需要注意的是,结构体struct sockaddr只在进行参数传递时使用,无论是在用户态还是在内核态,我们都把其强制转化为结构体struct sockaddr_in:
strcut sockaddr_in{
sa_family_t sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct in_addr{
__u32 s_addr;
}
__SOCK_SIZE__的值为16,所以,struct sockaddr中真正有用的数据只有8bytes。在我们的ping例子中,传入到内核的msghdr结构中:
msg.msg_name = { sa_family_t = MY_AF_INET, sin_port = 0, sin_addr.s_addr = 172.16.48.1 }
msg_msg_namelen = 16。
请求回显icmp包没有目的端地址的端口号。
第二组是msg_iov和msg_iovlen,记录这个消息的内容。msg_iov是一个指向结构体struct iovec的指针,实际上,确切地说,应该是一个结构体strcut iovec的数组。下面是该结构体的定义:
struct iovec{
void __user *iov_base;
__kernel_size_t iov_len;
};
iov_base指向数据包缓冲区,即参数buff,iov_len是buff的长度。msghdr中允许一次传递多个buff,以数组的形式组织在 msg_iov中,msg_iovlen就记录数组的长度(即有多少个buff)。在我们的ping程序的实例中:
msg.msg_iov = { struct iovec = { iov_base = { icmp头+填充字符'E' }, iov_len = 40 } }
msg.msg_len = 1
第三组是msg_control和msg_controllen,它们可被用于发送任何的控制信息,在我们的例子中,没有控制信息要发送。暂时略过。
第四组是msg_flags。其值即为传入的参数flags。raw协议不支持MSG_OOB向标志,即带外数据。向向内核发送msg时使用msghdr,netlink socket使用自己的消息头nlmsghdr和自己的消息地址sockaddr_nl:
struct sockaddr_nl
{
sa_family_t nl_family;
unsigned short nl_pad;
__u32 nl_pid;
__u32 nl_groups;
};
struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message */
__u16 nlmsg_type; /* Message type*/
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process PID */
};
过程如下:
struct msghdr msg;memset(&msg, 0, sizeof(msg));msg.msg_name = (void *)&(nladdr);msg.msg_namelen = sizeof(nladdr);
{/*初始化一个strcut nlmsghdr结构存,nlmsghdr为netlink socket自己的消息头部,并使iov->iov_base指向在这个结构*/
char buffer[] = "An example message";
struct nlmsghdr nlhdr;
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
strcpy(NLMSG_DATA(nlhdr),buffer);//将数据存放在消息头指向的数据地址
nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));
nlhdr->nlmsg_pid = getpid(); /* self pid */
nlhdr->nlmsg_flags = 0;
iov.iov_base = (void *)nlhdr;
iov.iov_len = nlh->nlmsg_len;
}
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
fd=socket(AF_NETLINK, SOCK_RAW, netlink_type);
sendmsg(fd,&msg,0)
8. struct sk_buff
http://blog.csdn.net/nuoruo/archive/2007/11/29/1906741.aspx
或者问 baidu or google
9. struct socket 、 sock 、proto_ops,proto