当前位置:首页 » 操作系统 » linux入口函数

linux入口函数

发布时间: 2022-09-11 13:19:23

⑴ 在linux内核中,注册字符设备驱动程序的函数是

字符设备驱动程序框架 1、写出open、write函数 2、告诉内核 1)、定义一个struct file_operations结构并填充好 static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_mole变量 */ .open = first_drv_open, .write = first_drv_write, }; 2)、把struct file_operations结构体告诉内核 major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核相关参数:第一个,设备号,0自动分配主设备号,否则为主设备号0-255 第二个:设备名第二个:struct file_operations结构体 4)、register_chrdev由谁调用(入口函数调用) static int first_drv_init(void) 5)、入口函数须使用内核宏来修饰 mole_init(first_drv_init); mole_init会定义一个结构体,这个结构体里面有一个函数指针指向first_drv_init这个函数,当我们加载或安装一个驱动时,内核会自动找到这个结构体,然后调用里面的函数指针,这个函数指针指向first_drv_init这个函数,first_drv_init这个函数就是把struct file_operations结构体告诉内核 6)、有入口函数就有出口函数 mole_exit(first_drv_exit); 最后加上协议 MODULE_LICENSE("GPL"); 3、mdev根据系统信息自动创建设备节点: 每次写驱动都要手动创建设备文件过于麻烦,使用设备管理文件系统则方便很多。在2.6的内核以前一直使用的是devfs,但是它存在许多缺陷。它创建了大量的设备文件,其实这些设备更本不存在。而且设备与设备文件的映射具有不确定性,比如U盘即可能对应sda,又可能对应sdb。没有足够的主/辅设备号。2.6之后的内核引入了sysfs文件系统,它挂载在/sys上,配合udev使用,可以很好的完成devfs的功能,并弥补了那些缺点。(这里说一下,当今内核已经使用netlink了)。 udev是用户空间的一个应用程序,在嵌入式中用的是mdev,mdev在busybox中。mdev是udev的精简版。首先在busybox中添加支持mdev的选项: Linux System Utilities ---> [*] mdev [*] Support /etc/mdev.conf [*] Support subdirs/symlinks [*] Support regular expressions substitutions when renaming device [*] Support command execution at device addition/removal 然后修改/etc/init.d/rcS: echo /sbin/mdev > /proc/sys/kernel/hotplug /sbin/mdev -s 执行mdev -s :以‘-s’为参数调用位于 /sbin目录写的mdev(其实是个链接,作用是传递参数给/bin目录下的busybox程序并调用它),mdev扫描 /sys/class 和 /sys/block 中所有的类设备目录,如果在目录中含有名为“dev”的文件,且文件中包含的是设备号,则mdev就利用这些信息为这个设备在/dev 下创建设备节点文件。一般只在启动时才执行一次 “mdev -s”。热插拔事件:由于启动时运行了命 令:echo /sbin/mdev > /proc/sys/kernel/hotplug ,那么当有热插拔事件产生时,内核就会调用位于 /sbin目录的mdev。这时mdev通过环境变量中的 ACTION 和 DEVPATH,来确定此次热插拔事件的动作以及影响了/sys中的那个目录。接着会看看这个目录中是否“dev”的属性文件,如果有就利用这些信息为 这个设备在/dev 下创建设备节点文件重新打包文件系统,这样/sys目录,/dev目录就有东西了下面是create_class的原型: #define class_create(owner, name) / ({ / static struct lock_class_key __key; / __class_create(owner, name, &__key); / }) extern struct class * __must_check __class_create(struct mole *owner, const char *name, struct lock_class_key *key); class_destroy的原型如下: extern void class_destroy(struct class *cls); device_create的原型如下: extern struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) __attribute__((format(printf, 5, 6))); device_destroy的原型如下: extern void device_destroy(struct class *cls, dev_t devt); 具体使用如下,可参考后面的实例: static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); 下面再来看一下应用程序如何找到这个结构体的在应用程序中我们使用open打开一个设备:如:open(/dev/xxx, O_RDWR); xxx有一个属性,如字符设备为c,后面为读写权限,还有主设备名、次设备名,我们注册时 通过register_chrdev(0, "first_drv", &first_drv_fops)(有主设备号,设备名,struct file_operations结构体)将first_drv_fops结构体注册到内核数组chrdev中去的,结构体中有open,write函数,那么应用程序如何找到它的,事实上是根据打开的这个文件的属性中的设备类型及主设备号在内核数组chrdev里面找到我们注册的first_drv_fops,实例代码: #include #include #include #include #include #include #include #include #include #include static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; static int first_drv_open(struct inode *inode, struct file *file) { //printk("first_drv_open\n"); /* 配置GPF4,5,6为输出 */ *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; //printk("first_drv_write\n"); _from_user(&val, buf, count); // _to_user(); if (val == 1) { // 点灯 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6)); } else { // 灭灯 *gpfdat |= (1<<4) | (1<<5) | (1<<6); } return 0; } static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_mole变量 */ .open = first_drv_open, .write = first_drv_write, }; int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核 firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon + 1; return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸载 class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); iounmap(gpfcon); } mole_init(first_drv_init); mole_exit(first_drv_exit); MODULE_LICENSE("GPL"); 编译用Makefile文件 KERN_DIR = /work/system/linux-2.6.22.6 all: make -C $(KERN_DIR) M=`pwd` moles clean: make -C $(KERN_DIR) M=`pwd` moles clean rm -rf moles.order obj-m += first_drv.o 测试程序: #include #include #include #include /* firstdrvtest on * firstdrvtest off */ int main(int argc, char **argv) { int fd; int val = 1; fd = open("/dev/xyz", O_RDWR); if (fd < 0) { printf("can't open!\n"); } if (argc != 2) { printf("Usage :\n"); printf("%s \n", argv[0]); return 0; } if (strcmp(argv[1], "on") == 0) { val = 1; } else { val = 0; } write(fd, &val, 4); return 0; }

⑵ 问个嵌入式linux中驱动编程的问题,有个模块 static int _init embed_hello_init (void)

1)embed_hello_init 不是结构体名,是函数名
2)int _init部分,int 表示函数的返回值类型,是整型
扣除_init去看,static int embed_hello_init (void),就是定义一个静态的无入参函数,返回值是整型。这些概念跟嵌入式,linux,驱动都没有任何关系,是C语法的概念。

回到_init,这是linux 内核编程的一个特殊宏,,展开是一个gcc的扩展属性语法,限制了函数链接时放elf文件的那个section。
定义大概如下(不同内核版本可能有差异):
#define __init __attribute__((__section__(".init")))

通过把init函数限制在一个固定的section,一个作用是在启动时简单遍历section调用初始化函数即可,另外一个作用是在初始化完成后,可以马上释放该section所占空间给系统用(因为初始化函数通常只在系统启动后执行一次)。

⑶ linux 能不能看到线程的入口函数

你好。 在分时系统里应该没什必要吧 setpriority/getpriority,这两个函数描述的是改变进程优先级。 但是在linux中线程就是一个轻量级的进程, 所以这两个函数是可以作用于单独的线程的 如果我的回答没能帮助您,请继续追问。

⑷ linux驱动的入口函数是不是只用mole_init()

这个是模块的入口,就这一个!Linux要的就是统一接口,你还希望它复杂一点吗?就这一个多好!

⑸ 在linux编程中若一个用户程序希望将一组数据传递给kernel有几种方式

教科书里的Linux代码例子都已作古,所以看到的代码不能当真,领会意思就行了
比如以前的init进程的启动代码
execve(init_filename,argv_init,envp_init);

现在改为
static void run_init_process(char *init_filename)
{
argv_init[0] = init_filename;
kernel_execve(init_filename, argv_init, envp_init);
}

好的,聪明人就发现,linux内核中调用用户空间的程序可以使用init这样的方式,调用 kernel_execve
不过内核还是提供了更好的辅助接口call_usermodehelper,自然最后也是调用kernel_execve

调用特定的内核函数(系统调用)是 GNU/Linux 中软件开发的原本就有的组成部分。但如果方向反过来呢,内核空间调用用户空间?确实有一些有这种特性的应用程序需要每天使用。例如,当内核找到一个设备, 这时需要加载某个模块,进程如何处理?动态模块加载在内核通过 usermode-helper 进程进行。
让我们从探索 usermode-helper 应用程序编程接口(API)以及在内核中使用的例子开始。 然后,使用 API 构造一个示例应用程序,以便更好地理解其工作原理与局限。
usermode-helper API
usermode-helper API 是个很简单的 API,其选项为用户熟知。例如,要创建一个用户空间进程,通常只要设置名称为 executable,选项都为 executable,以及一组环境变量(指向 execve 主页)。创建内核进程也是一样。但由于创建内核空间进程,还需要设置一些额外选项。

内核版本
本文探讨的是 2.6.27 版内核的 usermode-helper API。
表 1 展示的是 usermode-helper API 中一组关键的内核函数

表 1. usermode-helper API 中的核心函数

API 函数
描述

call_usermodehelper_setup 准备 user-land 调用的处理函数
call_usermodehelper_setkeys 设置 helper 的会话密钥
call_usermodehelper_setcleanup 为 helper 设置一个清空函数
call_usermodehelper_stdinpipe 为 helper 创建 stdin 管道
call_usermodehelper_exec 调用 user-land
表 2 中还有一些简化函数,它们封装了的几个内核函数(用一个调用代替多个调用)。这些简化函数在很多情况下都很有用,因此尽可能使用他们。

表 2. usermode-helper API 的简化

API 函数
描述

call_usermodehelper 调用 user-land
call_usermodehelper_pipe 使用 stdin 管道调用 user-land
call_usermodehelper_keys 使用会话密钥调用 user-land
让我们先浏览一遍这些核心函数,然后探索简化函数提供了哪些功能。核心 API 使用了一个称为subprocess_info 结构的处理函数引用进行操作。该结构(可在 ./kernel/kmod.c 中找到)集合了给定的 usermode-helper 实例的所有必需元素。该结构引用从 call_usermodehelper_setup 调用返回。该结构(以及后续调用)将会在 call_usermodehelper_setkeys(用于存储凭证)、call_usermodehelper_setcleanup 以及 call_usermodehelper_stdinpipe 的调用中进一步配置。最后,一旦配置完成,就可通过调用 call_usermodehelper_exec 来调用配置好的用户模式应用程序。

声明
该方法提供了一个从内核调用用户空间应用程序必需的函数。尽管这项功能有合理用途,还应仔细考虑是否需要其他实现。这是一个方法,但其他方法会更合适。
核心函数提供了最大程度的控制,其中 helper 函数在单个调用中完成了大部分工作。管道相关调用(call_usermodehelper_stdinpipe 和 helper 函数 call_usermodehelper_pipe)创建了一个相联管道供 helper 使用。具体地说,创建了管道(内核中的文件结构)。用户空间应用程序对管道可读,内核对管道可写。对于本文,核心转储只是使用 usermode-helper 管道的应用程序。在该应用程序(./fs/exec.c do_coremp())中,核心转储通过管道从内核空间写到用户空间。
这些函数与 sub_processinfo 以及 subprocess_info 结构的细节之间的关系如图 1 所示。
图 1. Usermode-helper API 关系

表 2 中的简化函数内部执行 call_usermodehelper_setup 函数和 call_usermodehelper_exec 函数。表 2 中最后两个调用分别调用的是 call_usermodehelper_setkeys 和 call_usermodehelper_stdinpipe。可以在 ./kernel/kmod.c 找到 call_usermodehelper_pipe 和 call_usermodehelper 的代码,在 ./include/linux/kmod.h 中找到 call_usermodhelper_keys 的代码。
为什么要从内核调用用户空间应用程序?
现在让我们看一看 usermode-helper API 所使用的内核空间。表 3 提供的并不是专门的应用程序列表,而是一些有趣应用的示例。

表 3. 内核中的 usermode-helper API 应用程序

应用程序
源文件位置

内核模块调用 ./kernel/kmod.c
电源管理 ./kernel/sys.c
控制组 ./kernel/cgroup.c
安全密匙生成 ./security/keys/request_key.c
内核事件交付 ./lib/kobject_uevent.c
最直接的 usermode-helper API 应用程序是从内核空间加载内核模块。request_mole 函数封装了 usermode-helper API 的功能并提供了简单的接口。在一个常用的模块中,内核指定一个设备或所需服务并调用 request_mole 来加载模块。通过使用 usermode-helper API,模块通过 modprobe 加载到内核(应用程序通过 request_mole 在用户空间被调用)。
与模块加载类似的应用程序是设备热插拔(在运行时添加或删除设备)。该特性是通过使用 usermode-helper API,调用用户空间的 /sbin/hotplug 工具实现的。
关于 usermode-helper API 的一个有趣的应用程序(通过 request_mole) 是文本搜索 API(./lib/textsearch.c)。该应用程序在内核中提供了一个可配置的文本搜索基础架构。该应用程序使用 usermode-helper API 将搜索算法当作可加载模块进行动态加载。在 2.6.30 内核版本中,支持三个算法,包括 Boyer-Moore(./lib/ts_bm.c),简单固定状态机方法(./lib/ts_fsm.c),以及 Knuth-Morris-Pratt 算法(./lib/ts_kmp.c)。
usermode-helper API 还支持 Linux 按照顺序关闭系统。当需要系统关闭电源时,内核调用用户空间的 /sbin/poweroff 命令来完成。其他应用程序如 表 3 所示,表中附有其源文件位置。
Usermode-helper API 内部
在 kernel/kmod.c 中可以找到 usermode-helper API 的源代码 和 API(展示了主要的用作内核空间的内核模块加载器)。这个实现使用 kernel_execve 完成脏工作(dirty work)。请注意 kernel_execve是在启动时开启 init 进程的函数,而且未使用 usermode-helper API。
usermode-helper API 的实现相当简单直观(见图 2)。usermode-helper 从调用call_usermodehelper_exec 开始执行(它用于从预先配置好的 subprocess_info 结构中清除用户空间应用程序)。该函数接受两个参数:subprocess_info 结构引用和一个枚举类型(不等待、等待进程中止及等待进程完全结束)。subprocess_info(或者是,该结构的 work_struct 元素)然后被压入工作队列(khelper_wq),然后队列异步执行调用。

图 2. usermode-helper API 内部实现

当一个元素放入 khelper_wq 时,工作队列的处理函数就被调用(本例中是__call_usermodehelper),它在 khelper 线程中运行。该函数从将 subprocess_info 结构出队开始,此结构包含所有用户空间调用所需信息。该路径下一步取决于 wait 枚举变量。如果请求者想要等整个进程结束,包含用户空间调用(UMH_WAIT_PROC)或者是根本不等待(UMH_NO_WAIT),那么会从 wait_for_helper 函数创建一个内核线程。否则,请求者只是等待用户空间应用程序被调用(UMH_WAIT_EXEC),但并不完全。这种情况下,会为____call_usermodehelper() 创建一个内核线程。
在 wait_for_helper 线程中,会安装一个 SIGCHLD 信号处理函数,并为 ____call_usermodehelper 创建另一个内核线程。但在 wait_for_helper 线程中,会调用 sys_wait4 来等待____call_usermodehelper 内核线程(由 SIGCHLD 信号指示)结束。然后线程执行必要的清除工作(为UMH_NO_WAIT 释放结构空间或简单地向 call_usermodehelper_exec() 回送一个完成报告)。
函数 ____call_usermodehelper 是实际让应用程序在用户空间启动的地方。该函数首先解锁所有信号并设置会话密钥环。它还安装了 stdin 管道(如果有请求)。进行了一些安装以后,用户空间应用程序通过 kernel_execve(来自 kernel/syscall.c)被调用,此文件包含此前定义的 path、argv 清单(包含用户空间应用程序名称)以及环境。当该进程完成后,此线程通过调用 do_exit() 而产生。
该进程还使用了 Linux 的 completion,它是像信号一样的操作。当 call_usermodehelper_exec 函数被调用后,就会声明 completion。当 subprocess_info 结构放入 khelper_wq 后,会调用wait_for_completion(使用 completion 变量作为参数)。请注意此变量会存储到 subprocess_info 结构作为 complete 字段。当子线程想要唤醒 call_usermodehelper_exec 函数,会调用内核方法complete,并判断来自 subprocess_info 结构的 completion 变量。该调用会解锁此函数使其能继续。可以在 include/linux/completion.h 中找到 API 的实现。
应用程序示例
现在,让我们看看 usermode-helper API 的简单应用。首先看一下标准 API,然后学习如何使用 helper 函数使事情更简单。
在该例中,首先开发了一个简单的调用 API 的可加载内核模块。清单 1 展示了样板模块功能,定义了模块入口和出口函数。这两个函数根据模块的 modprobe(模块入口函数)或 insmod(模块入口函数),以及 rmmod(模块出口函数)被调用。

清单 1. 模块样板函数

#include
#include
#include

MODULE_LICENSE( "GPL" );

static int __init mod_entry_func( void )
{
return umh_test();
}

static void __exit mod_exit_func( void )
{
return;
}

mole_init( mod_entry_func );
mole_exit( mod_exit_func );

usermode-helper API 的使用如 清单 2 所示,其中有详细描述。函数开始是声明所需变量和结构。以subprocess_info 结构开始,它包含所有的执行用户空间调用的信息。该调用在调用call_usermodehelper_setup 时初始化。下一步,定义参数列表,使 argv 被调用。该列表与普通 C 程序中的 argv 列表类似,定义了应用程序(数组第一个元素)和参数列表。需要 NULL 终止符来提示列表末尾。请注意这里的 argc 变量(参数数量)是隐式的,因为 argv 列表的长度已经知道。该例中,应用程序名是 /usr/bin/logger,参数是 help!,然后是 NULL 终止符。下一个所需变量是环境数组(envp)。该数组是一组定义用户空间应用程序执行环境的参数列表。本例中,定义一些常用的参数,这些参数用于定义 shell 并以 NULL 条目结束。

清单 2. 简单的 usermode_helper API 测试

static int umh_test( void )
{
struct subprocess_info *sub_info;
char *argv[] = { "/usr/bin/logger", "help!", NULL };
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };

sub_info = call_usermodehelper_setup( argv[0], argv, envp, GFP_ATOMIC );
if (sub_info == NULL) return -ENOMEM;

return call_usermodehelper_exec( sub_info, UMH_WAIT_PROC );
}

下一步,调用 call_usermodehelper_setup 来创建已初始化的 subprocess_info 结构。请注意使用了先前初始化的变量以及指示用于内存初始化的 GFP 屏蔽第四个参数。在安装函数内部,调用了kzalloc(分配内核内存并清零)。该函数需要 GFP_ATOMIC 或 GFP_KERNEL 标志(前者定义调用不可以休眠,后者定义可以休眠)。快速测试新结构(即,非 NULL)后,使用 call_usermodehelper_exec 函数继续调用。该函数使用 subprocess_info 结构以及定义是否等待的枚举变量(在 “Usermode-helper API 内部” 一节中有描述)。全部完成! 模块一旦加载,就可以在 /var/log/messages 文件中看到信息。
还可以通过 call_usermodehelper API 函数进一步简化进程,它同时执行 call_usermodehelper_setup和 call_usermodehelper_exec 函数。如清单 3 所示,它不仅删除函数,还消除了调用者管理subprocess_info 结构的必要性。

清单 3. 更简单的 usermode-helper API 测试

static int umh_test( void )
{
char *argv[] = { "/usr/bin/logger", "help!", NULL };
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL };

return call_usermodehelper( argv[0], argv, envp, UMH_WAIT_PROC );
}

请注意在清单 3 中,有着同样的安装并调用(例如初始化 argv 和 envp 数组)的需求。此处惟一的区别是 helper 函数执行 setup 和 exec 函数。

⑹ 在linux内核编程里“static int hello_init(void)”是什么意思

这应该是驱动编程里的例子吧,这是驱动入口函数,你加载驱动后,就先执行这个函数.

⑺ linux源码分析

linux的tcp-ip栈代码的详细分析

1.数据结构(msghdr,sk_buff,socket,sock,proto_ops,proto)

bsd套接字层,操作的对象是socket,数据存放在msghdr这样的数据结构:

创建socket需要传递family,type,protocol三个参数,创建socket其实就是创建一个socket实例,然后创建一个文件描述符结构,并且互相建立一些关联,即建立互相连接的指针,并且初始化这些对文件的写读操作映射到socket的read,write函数上来。

同时初始化socket的操作函数(proto_ops结构),如果传入的type参数是STREAM类型,那么就初始化为SOCKET->ops为inet_stream_ops,如果是DGRAM类型,则SOCKET-ops为inet_dgram_ops。对于inet_stream_ops其实是一个结构体,包含了stream类型的socket操作的一些入口函数,在这些函数里主要做的是对socket进行相关的操作,同时通过调用下面提到的sock中的相关操作完成socket到sock层的传递。比如在inet_stream_ops里有个inet_release的操作,这个操作除了释放socket的类型空间操作外,还通过调用socket连接的sock的close操作,对于stream类型来说,即tcp_close来关闭sock

释放sock。

创建socket同时还创建sock数据空间,初始化sock,初始化过程主要做的事情是初始化三个队列,receive_queue(接收到的数据包sk_buff链表队列),send_queue(需要发送数据包的sk_buff链表队列),backlog_queue(主要用于tcp中三次握手成功的那些数据包,自己猜的),根据family、type参数,初始化sock的操作,比如对于family为inet类型的,type为stream类型的,sock->proto初始化为tcp_prot.其中包括stream类型的协议sock操作对应的入口函数。

在一端对socket进行write的过程中,首先会把要write的字符串缓冲区整理成msghdr的数据结构形式(参见linux内核2.4版源代码分析大全),然后调用sock_sendmsg把msghdr的数据传送至inet层,对于msghdr结构中数据区中的每个数据包,创建sk_buff结构,填充数据,挂至发送队列。一层层往下层协议传递。一下每层协议不再对数据进行拷贝。而是对sk_buff结构进行操作。

⑻ Linux内核中select,poll和epoll的区别

随着2.6内核对epoll的完全支持,网络上很多的文章和示例代码都提供了这样一个信息:使用epoll代替传统的poll能给网络服务应用带来性能上的提升。但大多文章里关于性能提升的原因解释的较少,这里我将试分析一下内核(2.6.21.1)代码中poll与epoll的工作原理,然后再通过一些测试数据来对比具体效果。

POLL:

先说poll,poll或select为大部分Unix/Linux程序员所熟悉,这俩个东西原理类似,性能上也不存在明显差异,但select对所监控的文件描述符数量有限制,所以这里选用poll做说明。

poll是一个系统调用,其内核入口函数为sys_poll,sys_poll几乎不做任何处理直接调用do_sys_poll,do_sys_poll的执行过程可以分为三个部分:

1,将用户传入的pollfd数组拷贝到内核空间,因为拷贝操作和数组长度相关,时间上这是一个O(n)操作,这一步的代码在do_sys_poll中包括从函数开始到调用do_poll前的部分。

2,查询每个文件描述符对应设备的状态,如果该设备尚未就绪,则在该设备的等待队列中加入一项并继续查询下一设备的状态。查询完所有设备后如果没有一个设备就绪,这时则需要挂起当前进程等待,直到设备就绪或者超时,挂起操作是通过调用schele_timeout执行的。设备就绪后进程被通知继续运行,这时再次遍历所有设备,以查找就绪设备。这一步因为两次遍历所有设备,时间复杂度也是O(n),这里面不包括等待时间。相关代码在do_poll函数中。

3,将获得的数据传送到用户空间并执行释放内存和剥离等待队列等善后工作,向用户空间拷贝数据与剥离等待队列等操作的的时间复杂度同样是O(n),具体代码包括do_sys_poll函数中调用do_poll后到结束的部分。

EPOLL:

接下来分析epoll,与poll/select不同,epoll不再是一个单独的系统调用,而是由epoll_create/epoll_ctl/epoll_wait三个系统调用组成,后面将会看到这样做的好处。

先来看sys_epoll_create(epoll_create对应的内核函数),这个函数主要是做一些准备工作,比如创建数据结构,初始化数据并最终返回一个文件描述符(表示新创建的虚拟epoll文件),这个操作可以认为是一个固定时间的操作。

epoll是做为一个虚拟文件系统来实现的,这样做至少有以下两个好处:

1,可以在内核里维护一些信息,这些信息在多次epoll_wait间是保持的,比如所有受监控的文件描述符。

2, epoll本身也可以被poll/epoll;

具体epoll的虚拟文件系统的实现和性能分析无关,不再赘述。

在sys_epoll_create中还能看到一个细节,就是epoll_create的参数size在现阶段是没有意义的,只要大于零就行。

接着是sys_epoll_ctl(epoll_ctl对应的内核函数),需要明确的是每次调用sys_epoll_ctl只处理一个文件描述符,这里主要描述当op为EPOLL_CTL_ADD时的执行过程,sys_epoll_ctl做一些安全性检查后进入ep_insert,ep_insert里将 ep_poll_callback做为回掉函数加入设备的等待队列(假定这时设备尚未就绪),由于每次poll_ctl只操作一个文件描述符,因此也可以认为这是一个O(1)操作

ep_poll_callback函数很关键,它在所等待的设备就绪后被系统回掉,执行两个操作:

1,将就绪设备加入就绪队列,这一步避免了像poll那样在设备就绪后再次轮询所有设备找就绪者,降低了时间复杂度,由O(n)到O(1);

2,唤醒虚拟的epoll文件;

最后是sys_epoll_wait,这里实际执行操作的是ep_poll函数。该函数等待将进程自身插入虚拟epoll文件的等待队列,直到被唤醒(见上面ep_poll_callback函数描述),最后执行ep_events_transfer将结果拷贝到用户空间。由于只拷贝就绪设备信息,所以这里的拷贝是一个O(1)操作。

还有一个让人关心的问题就是epoll对EPOLLET的处理,即边沿触发的处理,粗略看代码就是把一部分水平触发模式下内核做的工作交给用户来处理,直觉上不会对性能有太大影响,感兴趣的朋友欢迎讨论。

POLL/EPOLL对比:

表面上poll的过程可以看作是由一次epoll_create/若干次epoll_ctl/一次epoll_wait/一次close等系统调用构成,实际上epoll将poll分成若干部分实现的原因正是因为服务器软件中使用poll的特点(比如Web服务器):

1,需要同时poll大量文件描述符;

2,每次poll完成后就绪的文件描述符只占所有被poll的描述符的很少一部分。

3,前后多次poll调用对文件描述符数组(ufds)的修改只是很小;

⑼ Linux的KVM模块中,kvm_main.c没有入口函数,不调用宏mole_init是怎么生成kvm.ko模块的

很明显,这个kvm.ko不是virt/kvm/生成的。
而是在 arch/(xxx)/kvm/生成的

⑽ linux多进程,每个进程都有1个main函数吗

简单的实现,没有添加同步机制,回头再添加上去,而且,我是在不同终端里面写的,你可以把两段代码,一个放到父进程,一个放到子进程...就可以了你说的这些API,自己man 一次,看看说明就知道用法了.... 楼上说的对齐的问题,我没有太注意..不过,不管你要共享什么,一个sizeof看看大小,一个memcpy拷贝,你就作为数据直接拷贝到共享内存区域就OK了...另外一边再拷贝回来,用一个结构体类型的指针指向你拷贝回来的数据,不就给这部分内存再规划成一个结构体了。。至于具体的, KEY 的含义,你需要了解linux的ipc机制。 #include #include #include #include #define BUF_SIZE 100 #define KEY 99 int main(void) { int shmid; char *shmptr; shmid=shmget(99,BUF_SIZE,IPC_CREAT|0666); if(shmid==-1) { printf("Shared Memory Created error...\n");exit(0); } shmptr=shmat(shmid,NULL,0); if(shmptr==(void*)-1) { printf("shmat error,shmptr= %d \n",shmptr); exit(1); } while(1) { printf("type strings into Shared Memory:"); fgets(shmptr,BUF_SIZE,stdin); } return 0; } 下面这段就每隔10秒钟扫描共享内存区域的内容: #include #include #include #include #define BUF_SIZE 100 #define KEY 99 int main(void) { int shmid; char *shmptr; shmid=shmget(99,BUF_SIZE,IPC_CREAT|0666); if(shmid==-1) { printf("Shared Memory Created error...\n");exit(0); } shmptr=shmat(shmid,NULL,0); if(shmptr==(void*)-1) { printf("shmat error,shmptr= %d \n",shmptr); exit(1); } while(1) { printf("Infomation in Shared Memory:"); printf("%s \n",shmptr); sleep(10); } return 0; }

热点内容
pythonforinkeys 发布:2024-05-19 01:55:44 浏览:792
电脑如何局域网共享文件夹 发布:2024-05-19 01:25:01 浏览:68
手机存储越大性能越好吗 发布:2024-05-19 01:14:28 浏览:176
我的世界hyp服务器怎么玩 发布:2024-05-19 00:51:25 浏览:801
手机如何解压百度云文件 发布:2024-05-19 00:32:24 浏览:905
centos使用python 发布:2024-05-18 23:39:48 浏览:869
幻影天龙脚本 发布:2024-05-18 23:38:17 浏览:714
编程的py 发布:2024-05-18 23:36:22 浏览:76
安卓系统怎么改序列号 发布:2024-05-18 23:28:16 浏览:785
c语言中实数 发布:2024-05-18 23:21:03 浏览:897