linux内核中常见的数据结构与操作

1。双向链表(列表)



The bidirectional list in the Linux kernel is connected by the structure struct list_head to each node. 这个结构将是链元素结构中的一个参数。



结构list_head {



结构list_head * *下,沪指;



};



链头的初始化,注意结构中的指针是null而不是初始化,但是初始化是初始化本身。如果在正常情况下设置为NULL,而不是指向自己,系统将崩溃,这是一个容易犯的错误。



#定义list_head_init(name){(姓名),(名字)}



#定义list_head(名称)



结构list_head名称= list_head_init(名称)



#定义init_list_head(PTR)做{



(PTR)->下=(PTR);(PTR)-> prev =(PTR);



}(0)



最常用的链表操作:



插入链头:



无效list_add(struct list_head *新,结构list_head *头);



插入列表尾部:



无效list_add_tail(struct list_head *新,结构list_head *头);



删除链表节点:



无效list_del(struct list_head入口);



将节点移动到另一个列表:



无效list_move(struct list_head *列表,结构list_head *头);



将节点移动到列表的末端:



无效list_move_tail(struct list_head *列表,结构list_head *头);



确定列表是否为空,返回1为空,0为非空。



国际list_empty(struct list_head *头);



将两个链表拼接在一起:



无效list_splice(struct list_head *列表,结构list_head *头);



获取节点指针:



#定义list_entry(PTR,类型成员)



((类)(char *)(PTR)-(长整型)((((((((型*)0)->成员))))))



遍历链表中的每个节点:



#定义list_for_each(POS,头)



对于(=(头)-下一个,预取(POS >下);POS!=(头);



下一步,预取(下一步)



反向循环列表中的每个节点:



#定义list_for_each_prev(POS,头)



对于(POS =(头)->昨日,预取(POS ->沪指);POS!=(头);



POS = POS ->昨日,预取(POS ->沪指))



例如:



lish_head(mylist);



结构my_list {



结构list_head列表;



int数据;



};



静态变量ini_list(void)



{



结构my_list * P;



int i;



对于(i = 0;i < 100;i + +){



P = kmalloc(sizeof(struct my_list),gfp_kernel);



list_add(P >列表,mylist);



}



}



在内存中形成下列结构的双向链表:



--------------------------------------------------------------- + +



| |



| mylist 99980 |



| + -- + + + + + + --------- --------- --------- + |



+ -> |下| ---> |名单。下一| ---> |名单。下一| --- >…——> |名单。下一|——+



| ---- | | --------- | | --------- | | --------- |



+ -- |沪指| < --- |列表。沪指| < --- |列表。沪指| < ---…< --- |列表。沪指| <——+



| +——+ | --------- | | --------- | | --------- | |



| |数据| |数据| |数据| |



| + + + + + --------- --------- --------- + |



| |



--------------------------------------------------------------- + +



知道链头能够遍历整个列表,如果list_add()插入新的节点,它是从链头下方向堆栈。



从链表中删除节点是很容易的。



静态del_item(struct my_list * P)



{



list_del(P >列表,mylist);



Kfree(P);



}



最重要的是list_entry宏。这个宏的想法是根据链中的元件结构的链头结构list_head地址导出的列表元素结构的实际地址。



#定义list_entry(PTR,类型成员)



((类)(char *)(PTR)-(长整型)((((((((型*)0)->成员))))))



PTR是链中的元件结构的链头结构list_head地址,如结构my_list



成员是在链的链头结构list_head元件结构参数的名称(如结构my_list)



Type is the type of element structure of the linked list (such as struct my_list)



计算的原理是通过从链头结构list_head地址减去链表元素结构的位置得到链单元结构的地址。



例如:



print_list静虚空(void)



{



结构list_head *电流;



结构my_list * P;



list_for_each(狗,mylist){



P = list_entry(CUR,结构my_list,列表);



printk(数据= % DN



}



}



优势:



通过这种方式,同样的数据处理方法可以用来描述所有双向链表,并且没有为每个链表编写更多的编辑函数。



缺点:



1)链头中的元素没有初始化,这与普通习惯不同。



2)仍然需要写自己单独的函数来删除整个列表,没有统一的治疗,因为它不能保证list_head头偏移地址的链结构在结构的所有列表元素是相同的,当然,如果链头结构list_head作为第一个参数列表元素的结构,它可以用于删除整个统一的功能列表。









2。哈希表



哈希表是适合需要整个空间元素的排序,但只可以快速找到场合的一个元素,是一种空间换时间的方法,也是线性形式的本质,但由一个大的线性表分小的线性表,因为它只需要找到一个小桌子,因此,搜索速度将检查线性表提高很多,最理想的情况是,小的线性表的数量,搜索速度提高了许多倍,通常小的线性表头集成到一个数组,大小是哈希表。



根据固定参数为每个单元分别计算密钥表设计速度哈希散列函数和哈希函数,计算表数不大于哈希索引值的元素,称需要放在对应的表的索引,一个固定的参数,计算结果往往是固定的,但不同参数值计算结果尽可能的平均指标值为多,哈希函数计算较为平均,表示在每个小表元素的数量将是相似的,所以搜索性能将better.hash功能也应该尽可能简单,以减少计算时间。通常的算法是通过调节积累参数,和一些哈希运算功能已被包括 / Linux / jhash。h,可直接使用。



哈希表在路由缓存表、状态连接表等方面使用较多。



例如,连接跟踪根据元组值计算散列:



/ / / / Netfilter IPv4网 / ip_conntrack_core C。



u_int32_t



hash_conntrack(const struct ip_conntrack_tuple *元组)



{



如果0 #



dump_tuple(元组);



# endif



返回(jhash_3words(元组-> SRC,IP)



(dst.ip ^元组元组-> -> DST。protonum),



(元组-> src.u.all |(元组-> dst.u.all<16)),



ip_conntrack_hash_rnd)% ip_conntrack_htable_size);



}



/ / / / jhash包括Linux。H



静态内联U32 jhash_3words(U32,U32 B、C U32 U32,initval)



{



a jhash_golden_ratio;



B = jhash_golden_ratio;



C = initval;



__jhash_mix(A,B,C);



返回C;



}









三.定时器(定时器)



Linux内核定时器由以下结构描述:



其中包括:



结构timer_list {



结构list_head列表;



无符号长过期;



无符号长数据;



空(*函数)(无符号长);



};



列表:定时器链表



过期:过期时间



函数:到期函数,当时间到期时调用的函数。



数据:在实际应用中,传输到到期函数的数据通常是指向结构的指针。



定时器的操作:



将计时器和挂起计时器添加到系统的计时器列表中:



add_timer extern void(结构timer_list *定时器);



从系统计时器列表中删除计时器并删除计时器:



extern int del_timer(struct timer_list *定时器);



(del_timer()的功能可能会失败,因为计时器已经不在系统定时器列表,即,它已被删除。)



对于SMP系统,删除计时器最好使用以下功能来防止冲突:



extern int del_timer_sync(struct timer_list *定时器);



修改计时器以修改计时器的过期时间:



国际mod_timer(struct timer_list *定时器,unsigned long到期);



通常用法:



结构timer_list通常作为数据结构的初始化参数初始化定时器的结构时,说在操作的有效进行,实现定时作用,通常作为一个超时处理,定时器函数为时间资源释放功能。注意:如果操作超时超时功能的系统是在底部的时钟中断了一半,不是很复杂的操作,如果你想完成一些复杂的操作,如发送数据到期后,可以直接在函数的失效,但要具体到顶部内核线程处理半送在函数的有效信号。



为了判断时间先后,在内核中定义了以下宏来判断:



#定义time_after(A,B)(长)(B)-(长)(一)<0)



#定义time_before(A,B)time_after(B,A)



#定义time_after_eq(A,B)((长)(一)-(长)(B)> = 0)



#定义time_before_eq(A,B)time_after_eq(B,A)



在哪里使用一种技术,因为Linux的时间是无符号的,在这里我们把它转换成一些符号后判断,可以解决时间问题,当然只是一个包装,大约两次当然不是自己判断出来的经验,具体的实验。









4。内核线程(kernel_thread)



在内核中的一个新的线程的建立可以与kernel_thread功能实现,这是内核/叉定义。C:



长kernel_thread(int(* FN)(void *),void* Arg,unsigned long的标志)



内核线程主函数;



线程主函数的参数;



标志:螺纹的标记;



内核线程函数通常被称为daemonize(背景)作为一个独立的线程运行,一些参数,然后设置线程的名称、信号处理等,这是没有必要的,然后进入一个死循环,这是本线的主要部分,不流通了操作,否则系统死在这,或某种事件驱动,如睡眠到来之前,唤醒操作的事件到来后,术后睡眠;或有规律的睡眠,醒来手术结束后去睡觉;或加入等待队列(按计划)计划执行时间。总之,你不能总是占用CPU。



下面是一个内核线程的实例:



国际start_context_thread(void)



{



静态结构完成启动__initdata = completion_initializer(启动);



kernel_thread(context_thread,启动,clone_fs | clone_files);



wait_for_completion(启动);



返回0;



}



静态变量context_thread(void *启动)



{



结构task_struct * curtask =电流;



declare_waitqueue(等等,curtask);



结构k_sigaction隐形刺客;



Daemonize();



Strcpy (curtask->comm, keventd);



keventd_running = 1;



keventd_task = curtask;



spin_lock_irq(curtask -> sigmask_lock);



Siginitsetinv(curtask ->受阻,sigmask(SIGCHLD));



recalc_sigpending(curtask);



spin_unlock_irq(curtask -> sigmask_lock);



完成((结构*)启动);



安装一个处理程序,所以sigcld是 / * * /交付



sa.sa.sa_handler = sig_ign;



sa.sa.sa_flags = 0;



Siginitset(sa.sa.sa_mask,sigmask(SIGCHLD));



do_sigaction(SIGCHLD、沙县、(结构k_sigaction *)0);



*



*如果任务队列中的某个函数重新添加自身



*任务队列调度(我们称之为国家task_running)



* /



为(;){



set_task_state(curtask,task_interruptible);



add_wait_queue(context_task_wq,等等);



如果(tq_active(tq_context))



set_task_state(curtask,task_running);



附表();



remove_wait_queue(context_task_wq,等等);



run_task_queue(tq_context);



wake_up(context_task_done);



如果(signal_pending(curtask)){



而(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);



}



}



}



5。结构的地址



在C语言中,一元结构和地址的地址结构是相同的,所以经常使用的第一个元素的结构出现在Linux内核地址在该结构的情况,地址,阅读代码时应注意这一点,宏相同的意义和list_entry。



如:



结构my_struct {



int;



b;



} C;



如果(C = = c){ / /总是正确的







}