`
javababy1
  • 浏览: 1173963 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Linux内核中的klist分析

阅读更多

Linux内核中的klist分析

分析的内核版本照样是2.6.38.5

Linux内核中的klist是在神级的双向链表上扩展而形成的。先给出一个图。

很清晰也很简单。

先说表头:

K_lock:是一把锁,用来锁表的。这个就不多啰嗦了。

k_list:双向链表,用来联系各节点及链表头。

getput:两个函数指针,是用来操作链表中的节点接口。

再说节点:

n_klist是一个空指针,随便用来指啥,但在我们的klist原语中是用来指向链表头的。另外其最低位用来做标志位。

n_node:双向链表,用来联系各节点及链表头。

n_ref:引用计数。

接下来我们来分析一下我们感兴趣的几个东西。

首先是

WARN_ON(condition)

BUG_ON(condition)

这两个宏。

经过分析后其在默认配置中被定义为:

#ifndef HAVE_ARCH_BUG_ON

#define BUG_ON(condition) do { if (condition) ; } while(0)

#endif

#ifndef HAVE_ARCH_WARN_ON

#define WARN_ON(condition) ({ \

int __ret_warn_on = !!(condition); \

unlikely(__ret_warn_on); \

})

#endif

可以认为这个宏是没用的。因为在klist中并没有对这个返回值作判断。

另外有的朋友可能会说linux内核中怎么会有这样的垃圾代码,会造成不必要的运行开销。其实我想说,你太低估linux内核开发者的水平了,这是一份国际顶尖级的高手写出来的代码,他们对编译器,操作系统,CPU体系结构的认识程度远不是你我所能达到的。说了这么多,让我们来一起仔细领会这些大牛的深厚功力。

我先写了个小例子程序:

#include "asm-generic/bug.h"

int main()

{

int aa = 0x0;

int condition = 0x77;

WARN_ON(condition);

aa = 0x88;

return 0;

}

然后利用命令:

gcc test.c -E -I/opt/kernel/linux-2.6.38/linux-2.6.38.5/include/ > test.txt

vi test.txt

我们可以看到有如下内容:

# 1 "test.c"

# 1 "<built-in>"

# 1 "<command line>"

# 1 "test.c"

# 1 "/opt/kernel/linux-2.6.38/linux-2.6.38.5/include/asm-generic/bug.h" 1

# 1 "/opt/kernel/linux-2.6.38/linux-2.6.38.5/include/linux/compiler.h" 1

# 5 "/opt/kernel/linux-2.6.38/linux-2.6.38.5/include/asm-generic/bug.h" 2

# 2 "test.c" 2

int main()

{

int aa = 0x0;

int condition = 0x77;

({ int __ret_warn_on = !!(condition); unlikely(__ret_warn_on); });

aa = 0x88;

return 0;

}

通过编译预处理,我们可以看到宏确实被展开了。

接下来我们反汇编这段代码:

我们输入命令:

gcc test.c -c -I/opt/kernel/linux-2.6.38/linux-2.6.38.5/include/ -o test.o –g
然后再gdb test.o

再:disassemble main 得到:

0x00000000 <main+0>: lea 0x4(%esp),%ecx

0x00000004 <main+4>: and $0xfffffff0,%esp

0x00000007 <main+7>: pushl -0x4(%ecx)

0x0000000a <main+10>: push %ebp

0x0000000b <main+11>: mov %esp,%ebp

0x0000000d <main+13>: push %ecx

0x0000000e <main+14>: sub $0x14,%esp

0x00000011 <main+17>: movl $0x0,-0x10(%ebp)

0x00000018 <main+24>: movl $0x77,-0xc(%ebp)

0x0000001f <main+31>: cmpl $0x0,-0xc(%ebp)

0x00000023 <main+35>: setne %al

0x00000026 <main+38>: movzbl %al,%eax

0x00000029 <main+41>: mov %eax,-0x8(%ebp)

0x0000002c <main+44>: mov -0x8(%ebp),%eax

0x0000002f <main+47>: mov %eax,(%esp)

0x00000032 <main+50>: call 0x33 <main+51>

0x00000037 <main+55>: movl $0x88,-0x10(%ebp)

0x0000003e <main+62>: mov $0x0,%eax

0x00000043 <main+67>: add $0x14,%esp

0x00000046 <main+70>: pop %ecx

0x00000047 <main+71>: pop %ebp

0x00000048 <main+72>: lea -0x4(%ecx),%esp

多了很多代码。这linux内核的作者这么蠢?

我们再看看inux内核根makefile下面的几行:

HOSTCC = gcc

HOSTCXX = g++

HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-p

ointer

HOSTCXXFLAGS = -O2

加了个O2

Ok,我们也加个O2

wwhs_klist]# gcc test.c -c -I/opt/kernel/linux-2.6.38/linux-2.6.38.5/include/ -o test.o -g –O2(后面分别用优化选项-O-O1-Os在我们关注的这个地方表现出来的效果是相同的)。

gdb test.o

disassemble main

得到:

0x00000000 <main+0>: lea 0x4(%esp),%ecx

0x00000004 <main+4>: and $0xfffffff0,%esp

0x00000007 <main+7>: pushl -0x4(%ecx)

0x0000000a <main+10>: push %ebp

0x0000000b <main+11>: mov %esp,%ebp

0x0000000d <main+13>: push %ecx

0x0000000e <main+14>: sub $0x4,%esp

0x00000011 <main+17>: movl $0x1,(%esp)

0x00000018 <main+24>: call 0x19 <main+25>

0x0000001d <main+29>: add $0x4,%esp

0x00000020 <main+32>: xor %eax,%eax

0x00000022 <main+34>: pop %ecx

0x00000023 <main+35>: pop %ebp

0x00000024 <main+36>: lea -0x4(%ecx),%esp

0x00000027 <main+39>: ret

哈哈。看到了吧,全都被优化掉了,所以加的这些东西对我们的性能不会造成任何影响!

我晕。好像偏题了,不好意思。

不过发现klist除了这个以外没有别的值得讲的。

值得一提的是:

个人觉得klist中有些函数的命名不是非常的好,容易让人误解,经过思考后发现klist的命名虽然是有规律的,但是说实话,确实可以做得更好。

另外有一个值得一讲的是:

void klist_remove(struct klist_node *n)

{

struct klist_waiter waiter;

waiter.node = n;

waiter.process = current;

waiter.woken = 0;

spin_lock(&klist_remove_lock);

list_add(&waiter.list, &klist_remove_waiters);

spin_unlock(&klist_remove_lock);

klist_del(n);

for (;;) {

set_current_state(TASK_UNINTERRUPTIBLE);

if (waiter.woken)

break;

schedule();

}

__set_current_state(TASK_RUNNING);

}

我们分析一下:

struct klist_waiter waiter;

waiter.node = n;

waiter.process = current; //这是个任务结构体,把当前任务结构体保存起来

waiter.woken = 0; //这是用于唤醒任务的标记

spin_lock(&klist_remove_lock);

list_add(&waiter.list, &klist_remove_waiters); //waiter.list链入klist_remove_waiters

spin_unlock(&klist_remove_lock);

接下来:

void klist_del(struct klist_node *n)

{

klist_put(n, true);

}

static void klist_put(struct klist_node *n, bool kill)

{

struct klist *k = knode_klist(n);

void (*put)(struct klist_node *) = k->put;

spin_lock(&k->k_lock);

if (kill)

knode_kill(n);

if (!klist_dec_and_del(n))

put = NULL;

spin_unlock(&k->k_lock);

if (put)

put(n);

}

重点在:

static int klist_dec_and_del(struct klist_node *n)

{

return kref_put(&n->n_ref, klist_release);

}

int kref_put(struct kref *kref, void (*release)(struct kref *kref))

{

WARN_ON(release == NULL);

WARN_ON(release == (void (*)(struct kref *))kfree);

if (atomic_dec_and_test(&kref->refcount)) { //一定要把引用计数消耗完才能调用release()

release(kref);

return 1;

}

return 0;

}

重点在:

static void klist_release(struct kref *kref)

{

struct klist_waiter *waiter, *tmp;

struct klist_node *n = container_of(kref, struct klist_node, n_ref);

WARN_ON(!knode_dead(n));

list_del(&n->n_node);

spin_lock(&klist_remove_lock);

list_for_each_entry_safe(waiter, tmp, &klist_remove_waiters, list) {

if (waiter->node != n)

continue;

waiter->woken = 1;

mb();

wake_up_process(waiter->process);

list_del(&waiter->list);

}

spin_unlock(&klist_remove_lock);

knode_set_klist(n, NULL);

}

到这里我们可以看到:

waiter->woken = 1;

mb();

标记也打了,内存屏障也设了(多任务的时候用这个玩意可以把寄存器的值回写到内存,防止编译器优化产生BUG,其最主要的函数是编译器内置的,有兴趣的同志可以自行学习一下)。接下就是用wake_up_process(waiter->process)要换醒我们之前设置好的进程了。

回到我们之前的地方:

void klist_remove(struct klist_node *n)

{

struct klist_waiter waiter;

waiter.node = n;

waiter.process = current;

waiter.woken = 0;

spin_lock(&klist_remove_lock);

list_add(&waiter.list, &klist_remove_waiters);

spin_unlock(&klist_remove_lock);

klist_del(n);

for (;;) {

set_current_state(TASK_UNINTERRUPTIBLE);

if (waiter.woken)

break;

schedule();

}

__set_current_state(TASK_RUNNING);

}

跳进循环:

set_current_state(TASK_UNINTERRUPTIBLE) 设置当前任务为不可中断状态,

接下来就是判断我们之前打的标记了,所以前面的标记如果没打的话,就会继续调用schedule()来切换任务,只有当标记被置了1(也就是说只有引用计数被消耗完了),才会跳出循环。这是设计的好的地方,可以确保在多任务环境下,所有调用者都有机会释放,但也是容易出问题的地方,如果不小心控制引用计数,一个死循环就这么产生了。

接下来再用__set_current_state(TASK_RUNNING)将任务状态切成正在行运状态。

分享到:
评论

相关推荐

    4klist:将4k版本的列表从thedigitalbits.com转储到CSV

    复制并粘贴到您选择的电子表格软件中! 要改为输出到文件,请设置FILE ENV var以及输出路径: FILE=/Users/rob/Desktop/4k-releases.csv ruby 4klist.rb 转储文件 dump.html文件是dump.html列表页面HTML源,用于...

    r0klist-开源

    一个可搜索、可排序的 PHP 文件列表器,可为图像创建缩略图,并为 mp3 创建带有 ID3 标签的播放/暂停按钮。 可以在站点范围内的多个目录中轻松使用。 演示:http://r0klist.sf.net

    kollection:Kotlin的不可变集合库和其他实用程序

    但这与KList的不同之处在于,对KStream的尾部进行了延迟计算。 因此, KStream可以表示一个无限流。 例如, ones是一个无限流,仅由1组成,如下所示: val ones : KStream &lt; Int&gt; = 1 cons { ones } 选件 KOption

    infrastructure-integration-test:斯坦福数字存储库的集成测试

    确保您拥有有效的,未过期的Kerberos票证(使用klist进行验证,或运行kinit进行刷新) 有关允许SSH凭据委派的说明,请参阅下面的“ Configuration部分。 运行测试 默认情况下,集成测试在SDR登台环境中运行: ...

    第68天:内网安全-域横向PTH&PTK&PTT哈希票据传递1

    2.mimikatz # kerberos::purge 3.利用 ms14-068 生成 TGT 数据 4.票据注入内存 5.查看凭证列表 klist 6.利

    kerberos-android-ndk:使用交叉编译的MIT Kerberos库和wolfSSL嵌入式SSL库的示例Android Kerberos应用程序

    这是一个示例Android NDK应用程序,提供了围绕MIT Kerberos kinit,klist,kvno和kdestroy客户端应用程序的GUI包装。 它还提供了一个使用Java GSS-API接口的示例客户端。 GSS-API接口是现有本机MIT GSS-API库的Java...

    c#源码转java源码的-klib:一个独立的轻量级C库

    klist.h:通用单链接列表和。 kstring。{h,c}:基本的字符串库。 kmath。{h,c}:数值例程,包括,基本和一些特殊的数学函数。 :具有类似getopt_long的API的可移植命令行参数解析器。 适用于更具体用例的组件 ksa....

    MS14-068AD域提权神器

    本地WINDOWS: 1.python.exe ms14-068.py -u user-a-1@dom-a.loc -s S-1-5-21-557603841-...klist net use \\DC2.secpulse.local\admin$ //注:使用IP可能会失败 dir \\DC2.secpulse.local\c$ 看看有木有权限 好运~

    k20:@edt ASIX M11-SAD巴塞罗那足球俱乐部。 Imatges KERBEROS。 诅咒2020-2021

    的Kerberos@edt ASIX M11-SAD Curs 2020-2021 Podeu trobar les imatges docker al Dockehub de Podeu trobar ladocumentaciódelmòdula ASIX M11-SAD巴塞罗那足球... 简单性aine eines kinit,klist i kdestroy(无pa

    Python通过kerberos安全认证操作kafka方式

    conf目录下krb5.conf和kafka.keytab和jaas.conf拷贝到客户端机器的etc目录, 同时,krb5.conf中的kdc集群主机名和IP配置到客户端机器hosts配置文件中 3.Kinit客户端通过kerberos认证 获取Principal klist -kt kafka....

    w2k_scripts

    make_klist_band.py analytics_w2k.py 以上が基本となるスクリプトで,それぞれ独立に动きます。これらを组み合わせて様々な计算が可能です。 例として, mapping.py conv_check.py NL_calc.py を添付していま...

    Windows Server 2003 Resource Kit Tools

    Klist.exe: Kerberos List Krt.exe: Certification Authority Key Recovery Lbridge.cmd: L-Bridge Linkd.exe Linkspeed.exe: Link Speed,连接速度 List.exe: List Text File Tool,察看文本文件 ...

    Windouws 64 位Tomcat7.0.40 + 64位jdk1.7.0u21 绿色版.part1

    │ │ │ │ klist.exe │ │ │ │ ktab.exe │ │ │ │ libxml2.dll │ │ │ │ libxslt.dll │ │ │ │ management.dll │ │ │ │ mlib_image.dll │ │ │ │ msvcr100.dll │ │ │ │ net.dll │ │ │ ...

Global site tag (gtag.js) - Google Analytics