全装备海盗:网卡驱动注册到PCI总线这一过程的分析

来源:百度文库 编辑:偶看新闻 时间:2024/04/24 21:24:07
大家好,最近在看网络部分的代码,目前看到了网卡的初始化部分。书上讲到的内容主要是网卡驱动程序对网卡自身的初始化部分,即网卡驱动的probe函数是如何执行的,而很少讲到网卡是如何注册到系统中去的这一部分。
    现在的网卡大部分都是连接到PCI总线上的。因此,网卡驱动是如何连接到PCI总线,又是如何与网卡设备联系起来,网卡在注册的最后又是
如何调用到该网卡的probe函数的,这一个过程将在后面的文章中进行描述。整个文章分成两个部分,第一部分是讲解总线、设备以及驱动三者的联系,为第二
部分具体讲解PCI总线、网卡设备和驱动做一点铺垫。
    由于我在这方面也是初学,之所以想总结出来是想到在总结的过程中对自己的学习也是一个梳理的过程。所以有什么地方写得不好的,还请各位多多指正,非常感谢!也希望能在这里结识更多的朋友。
    在总结的过程中参考了下面一些资料,在此表示感谢:
[1]
[2]
[3]
[4]  3rd Edition.
1. 总线、设备和驱动
1.1 简单介绍
        Linux设备模型中三个很重要的概念就是总线、设备和驱动,即bus,device和driver。它们分别对应的数据结构分别为struct bus_type,struct device和struct device_driver。
        总线是处理器与一个或多个设备之间的通道,在设备模型中,所有的设备都通过总线相连。在最底层,Linux系统中的每一个设备都用device结构的一个实例来表示。而驱动则是使总线上的设备能够完成它应该完成的功能。
在系统中有多种总线,如PCI总线、SCSI总线等。系统中的多个设备和驱动是通过总线让它们联系起来的。在bus_type中两个很重要的成员就是
struct kset drivers和struct kset devices。它分别代表了连接在这个总线上的两个链,一个是设备链表,另一个则是
设备驱动链表。也就是说,通过一个总线描述符,就可以找到挂载到这条总线上的设备,以及支持该总线的不同的设备驱动程序。
1.2 总线、设备与驱动的绑定
在系统启动时,它会对每种类型的总线创建一个描述符,并将使用该总线的设备链接到该总线描述符的devices链上来。也即是说在系统初始化时,它会扫描
连接了哪些设备,并且为每个设备建立一个struce device变量,然后将该变量链接到这个设备所连接的总线的描述符上去。另一方面,每当加载了一
个设备驱动,则系统也会准备一个struct device_driver结构的变量,然后再将这个变量也链接到它所在总线的描述符的drivers链上
去。
对于设备来说,在结构体struct device中有两个重要的成员,一个是struct bus_type *bus,另一个是
struct device_driver *driver。bus成员就表示该设备是链接到哪一个总线上的,而driver成员就表示当前设备是由哪个
驱动程序所驱动的。对于驱动程序来说,在结构体struct device_driver中也有两个成员,struct bus_type *bus和
struct list_head devices,这里的bus成员也是指向这个驱动是链接到哪个总线上的,而devices这个链表则是表示当前这个
驱动程序可以去进行驱动的那些设备。一个驱动程序可以支持一个或多个设备,而一个设备则只会绑定给一个驱动程序。
对于device与device_driver之间建立联系的方式,主要有两种方式。第一种,在计算机启动的时候,总线开始扫描连接在其上的设备,为每个
设备建立一个struct device变量并链接到该总线的devices链上,然后开始初始化不同的驱动程序,驱动程序到它所在的总线的
devices链上去遍历每一个还没有被绑定给某个驱动的设备,然后再查看是否能够支持这种设备,如果它能够支持这种设备,则将这个设备与这个驱动联系起
来。即,将这个设备的device变量加到驱动的devices链上,同时让struct device中的device_driver指向当前这个驱
动。第二种则是热插拔。也即是在系统运行时插入了设备,此时内核会去查找在该bus链上注册了的device_driver,然后再将设备与驱动联系起
来。设备与驱动根据什么规则联系起来,它们是如何被联系起来的代码我们将在后面的章节进行详细的描述。
1.3 PCI总线
PCI是一种在CPU与I/O设备之间进行高速数据传输的一种总线。有很多设备都是使用PCI总线的,网卡就是其中之一。我们在前面讲了那些总线、设备与
驱动方面的知识,原因就在于网卡是连接到PCI总线上,所以PCI总线、网卡设备以及网卡驱动就成了我们研究网卡的一个很重要的线索,尤其是在网络的链路
层部分。下图显示了在一个系统中PCI设备的一个框图:
[attach]226528[/attach]
        PCI子系统声明了一个bus_type结构,为pci_bus_type。它就是PCI总线的描述符。在这个变量上,链接了PCI设备以及支持PCI设备的驱动程序。
1.4 PCI设备与驱动
PCI设备通常由一组参数唯一地标识,它们被vendorID,deviceID和class nodes所标识,即设备厂商,型号等,这些参数保存在
pci_device_id结构中。每个PCI设备都会被分配一个pci_dev变量,内核就用这个数据结构来表示一个PCI设备。
所有的PCI驱动程序都必须定义一个pci_driver结构变量,在该变量中包含了这个PCI驱动程序所提供的不同功能的函数,同时,在这个结构中也包
含了一个device_driver结构,这个结构定义了PCI子系统与PCI设备之间的接口。在注册PCI驱动程序时,这个结构将被初始化,同时这个
pci_driver变量会被链接到pci_bus_type中的驱动链上去。
        在pci_driver中有一个成员struct pci_device_id *id_table,它列出了这个设备驱动程序所能够处理的所有PCI设备的ID值。
1.5 PCI设备与驱动的绑定过程
下面描述一下对于PCI设备与驱动绑定的过程。首先在系统启动的时候,PCI总线会去扫描连接到这个总线上的设备,同时为每一个设备建立一个
pci_dev结构,在这个结构中有一个device成员,并将这些pci_dev结构链接到PCI总线描述符上的devices链。如下图所示:
[attach]226529[/attach]
        第二步是当PCI驱动被加载时,pci_driver结构体将被初始化,这一过程在函数pci_register_driver中:
        drv->driver.bus = &pci_bus_type;
        drv->driver.probe = pci_device_probe;
最后会调用driver_register(&drv->driver)将这个PCI驱动挂载到总线描述符的驱动链上。同时在注册的过程
中,会根据pci_driver中的id_table中的ID值去查看该驱动支持哪些设备,将这些设备挂载到pci_driver中的devices链中
来。如下图所示:
[attach]226530[/attach]
        对于不同的设备,可能驱动程序也不一样,因此,对于上图中的Dev3,可能就需要另外一个驱动程序来对其进行驱动。所以当加载了Dev3的驱动程序时,其示意图如下图所示:
[attach]226531[/attach]
        上面这三个示意图就描述了总线、设备以及驱动在系统中是如何进行相互联系的。前面对于驱动注册这些函数的描述较为简单,因为网卡是一个PCI设备,因此在后面具体地讲到网卡注册时再来详细地讲解和PCI相关的注册等函数。
1.6 小结
本部分主要讲解了总线、设备以及驱动方面的一些知识,由于网卡是一个PCI设备,因此具体地讲到了一点PCI总线、PCI设备及相应的PCI驱动方面的知
识,但是由于PCI本身就是很大的一个子系统,因此这里不可能对其进行详细地讲解,在后面对网卡的分析中,将对网卡中涉及到的和PCI相关的部分进行讲
解。

PCI

init1

init2

init3

scutan
回复于:2008-12-15 22:47:47

2. 网卡在PCI层的注册
2.1 数据结构
        前面第一章讲了总线、设备以及驱动方面的关系,也讲到了大多数网卡设备实际上是一个PCI设备。因此,本章就讲解网卡设备在注册时是如何注册到PCI总线上去的。在这里,以Intel的E100网卡驱动进行讲解。
        前面讲到每个PCI设备都由一组参数唯一地标识,这些参数保存在结构体pci_device_id中,如下所示:
struct pci_device_id {
        __u32 vendor, device;                /* Vendor and device ID or PCI_ANY_ID*/
        __u32 subvendor, subdevice;        /* Subsystem ID's or PCI_ANY_ID */
        __u32 class, class_mask;        /* (class,subclass,prog-if) triplet */
        kernel_ulong_t driver_data;        /* Data private to the driver */
};
        每个PCI设备驱动都有一个pci_driver变量,它描述了一个PCI驱动的信息,如下所示:
struct pci_driver {
        struct list_head node;
        char *name;
        const struct pci_device_id *id_table;        /* must be non-NULL for probe to be called */
        int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);        /* New device inserted */
        void (*remove) (struct pci_dev *dev);        /* Device removed (NULL if not a hot-plug capable driver) */
        int  (*suspend) (struct pci_dev *dev, pm_message_t state);        /* Device suspended */
        int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
        int  (*resume_early) (struct pci_dev *dev);
        int  (*resume) (struct pci_dev *dev);                        /* Device woken up */
        int  (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);   /* Enable wake event */
        void (*shutdown) (struct pci_dev *dev);
        struct pci_error_handlers *err_handler;
        struct device_driver        driver;
        struct pci_dynids dynids;
        int multithread_probe;
};
        每个PCI驱动中都有一个id_table成员变量,记录了当前这个驱动所能够进行驱动的那些设备的ID值。
        对于E100网卡驱动来说,它的pci_driver变量定义为:
static struct pci_driver e100_driver = {
        .name =         DRV_NAME,
        .id_table =     e100_id_table,
        .probe =        e100_probe,
        .remove =       __devexit_p(e100_remove),
#ifdef CONFIG_PM
        /* Power Management hooks */
        .suspend =      e100_suspend,
        .resume =       e100_resume,
#endif
        .shutdown =     e100_shutdown,
        .err_handler = &e100_err_handler,
};
        里面e100_id_table就表示该E100驱动所能够支持的PCI设备的ID号,其定义为:
#define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {\
        PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, \
        PCI_CLASS_NETWORK_ETHERNET
当PCI层检测到一个PCI设备能够被某PCI驱动所支持时(这是通过函数pci_match_one_device来进行检测的),就会调用这个PCI
驱动上的probe函数,在该函数中会对该特定的PCI设备进行一些具体的初始化等操作。比如对于E100设备驱动来说,其probe函数为
e100_probe。在这个函数中,会对网卡设备进行初始化。
        e100_probe主要就涉及到网卡设备net_device的初始化,我们现在先来关注一下从网卡注册一直到调用e100_probe这一个过程的整个流程。
2.2 E100初始化
        E100驱动程序的初始化是在函数e100_init_module()中的,如下:
static int __init e100_init_module(void)
{
        if(((1
        在这个函数中,调用了pci_register_driver()函数,对e100_driver这个驱动进行注册。
2.3 PCI注册
        在前面我们已经看到,PCI的注册就是将PCI驱动程序挂载到其所在的总线的drivers链,同时扫描PCI设备,将它能够进行驱动的设备挂载到driver上的devices链表上来,这里,我们将详细地查看这整个流程的函数调用关系。
        pci_register_driver()->__pci_register_driver()
/**
* __pci_register_driver - register a new pci driver
* @drv: the driver structure to register
* @owner: owner module of drv
* @mod_name: module name string
*
* Adds the driver structure to the list of registered drivers.
* Returns a negative value on error, otherwise 0.
* If no error occurred, the driver remains registered even if
* no device was claimed during registration.
*/       
int __pci_register_driver(struct pci_driver *drv, struct module *owner, const char *mod_name);
        在函数中有几个初始化语句:
        drv->driver.name = drv->name;
        drv->driver.bus = &pci_bus_type;
        drv->driver.owner = owner;
        drv->driver.mod_name = mod_name;
        即是将PCI设备中的driver变量的总线指向pci_bus_type这个总线描述符,同时设置驱动的名字等。
        pci_bus_type定义如下:
struct bus_type pci_bus_type = {
        .name                = "pci",
        .match                = pci_bus_match,
        .uevent                = pci_uevent,
        .probe                = pci_device_probe,
        .remove                = pci_device_remove,
        .suspend        = pci_device_suspend,
        .suspend_late        = pci_device_suspend_late,
        .resume_early        = pci_device_resume_early,
        .resume                = pci_device_resume,
        .shutdown        = pci_device_shutdown,
        .dev_attrs        = pci_dev_attrs,
};
        然后再调用函数driver_register(&drv->driver);通过这个函数将这个PCI驱动中的struct device_driver driver成员变量注册到系统中去。
        pci_register_driver()->__pci_register_driver()->driver_register()
        driver_register()代码如下:
/**
*        driver_register - register driver with bus
*        @drv:        driver to register
*
*        We pass off most of the work to the bus_add_driver() call,
*        since most of the things we have to do deal with the bus
*        structures.
*
*        The one interesting aspect is that we setup @drv->unloaded
*        as a completion that gets complete when the driver reference
*        count reaches 0.
*/
int driver_register(struct device_driver * drv)
{
        if ((drv->bus->probe && drv->probe) ||
            (drv->bus->remove && drv->remove) ||
            (drv->bus->shutdown && drv->shutdown)) {
                printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods\n", drv->name);
        }
        klist_init(&drv->klist_devices, NULL, NULL);
        init_completion(&drv->unloaded);
        return bus_add_driver(drv);
}
        klist_init()是为设备驱动的klist_devices成员进行初始化,这个klist_devices是一个对链表进行操作的包裹结构,它会链接这个驱动能够支持的那些设备。
        最后就调用bus_add_driver()函数。这个函数的功能就是将这个驱动加到其所在的总线的驱动链上。
        pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()
        在bus_add_driver()函数中,最重要的是调用driver_attach()函数,其定义如下:
/**
*        driver_attach - try to bind driver to devices.
*        @drv:        driver.
*
*        Walk the list of devices that the bus has on it and try to
*        match the driver with each one.  If driver_probe_device()
*        returns 0 and the @dev->driver is set, we've found a
*        compatible pair.
*/
int driver_attach(struct device_driver * drv)
{
        return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
        该函数遍历这个驱动所在的总线上的所有设备,然后将这些设备与当前驱动进行匹配,以检测这个驱动是否能够支持某个设备,也即是将设备与驱动联系起来。
        bus_for_each_dev函数是扫描在drv->bus这个总线上的所有设备,然后将每个设备以及当前驱动这两个指针传递给__driver_attach函数。
pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()
        __driver_attach()函数是将驱动与设备联系起来的函数。
static int __driver_attach(struct device * dev, void * data)
{
        struct device_driver * drv = data;
        /*
         * Lock device and try to bind to it. We drop the error
         * here and always return 0, because we need to keep trying
         * to bind to devices and some drivers will return an error
         * simply if it didn't support the device.
         *
         * driver_probe_device() will spit a warning if there
         * is an error.
         */
        if (dev->parent)        /* Needed for USB */
                down(&dev->parent->sem);
        down(&dev->sem);
        if (!dev->driver)
                driver_probe_device(drv, dev);
        up(&dev->sem);
        if (dev->parent)
                up(&dev->parent->sem);
        return 0;
}
        在函数中有两条语句:
        if (!dev->driver)
                driver_probe_device(drv, dev);
        也即是判断当前设备是否已经注册了一个驱动,如果没有注册驱动,则调用driver_probe_device()函数。
pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()
        如下:
/**
* driver_probe_device - attempt to bind device & driver together
* @drv: driver to bind a device to
* @dev: device to try to bind to the driver
*
* First, we call the bus's match function, if one present, which should
* compare the device IDs the driver supports with the device IDs of the
* device. Note we don't do this ourselves because we don't know the
* format of the ID structures, nor what is to be considered a match and
* what is not.
*
* This function returns 1 if a match is found, an error if one occurs
* (that is not -ENODEV or -ENXIO), and 0 otherwise.
*
* This function must be called with @dev->sem held.  When called for a
* USB interface, @dev->parent->sem must be held as well.
*/
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
        struct stupid_thread_structure *data;
        struct task_struct *probe_task;
        int ret = 0;
        if (!device_is_registered(dev))
                return -ENODEV;
        if (drv->bus->match && !drv->bus->match(dev, drv))
                goto done;
        pr_debug("%s: Matched Device %s with Driver %s\n",
                 drv->bus->name, dev->bus_id, drv->name);
        data = kmalloc(sizeof(*data), GFP_KERNEL);
        if (!data)
                return -ENOMEM;
        data->drv = drv;
        data->dev = dev;
        if (drv->multithread_probe) {
                probe_task = kthread_run(really_probe, data,
                                         "probe-%s", dev->bus_id);
                if (IS_ERR(probe_task))
                        ret = really_probe(data);
        } else
                ret = really_probe(data);
done:
        return ret;
}       
        该函数首先会调用总线上的match函数,以判断当前的PCI驱动能否支持该PCI设备,如果可以,则继续往后面执行。
        drv->bus->match函数也即是pci_bus_type中的match成员变量,它为pci_bus_match函数。
pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->pci_bus_match()
/**
* pci_bus_match - Tell if a PCI device structure has a matching PCI device id structure
* @dev: the PCI device structure to match against
* @drv: the device driver to search for matching PCI device id structures
*
* Used by a driver to check whether a PCI device present in the
* system is in its list of supported devices. Returns the matching
* pci_device_id structure or %NULL if there is no match.
*/
static int pci_bus_match(struct device *dev, struct device_driver *drv)
{
        struct pci_dev *pci_dev = to_pci_dev(dev);
        struct pci_driver *pci_drv = to_pci_driver(drv);
        const struct pci_device_id *found_id;
        found_id = pci_match_device(pci_drv, pci_dev);
        if (found_id)
                return 1;
        return 0;
}
pci_bus_match函数的作用就是将PCI设备与PCI驱动进行比较以检查该驱动是否能够支持这个设备。在函数的最前面是两个宏
to_pci_dev和to_pci_driver。因为在函数执行的过程中,虽然最开始传进来的是pci_driver结构与pci_dev结构,但是
在执行的时候却取了这两个结构体中的device_driver和device成员变量,所以现在就要通过这两个成员变量找到之前对应的
pci_driver和pci_dev结构的地址。
#define        to_pci_dev(n) container_of(n, struct pci_dev, dev)
#define        to_pci_driver(drv) container_of(drv,struct pci_driver, driver)
这两个宏在 3rd书上有相应的讲解,这里也就是找到E100的pci_driver:
e100_driver以及该网卡设备的pci_dev结构。现在就要对它们进行比较以看它们之间是否能够联系起来。这是通过函数
pci_match_device实现的。
pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->pci_bus_match()->pci_match_device()
/**
* pci_match_device - Tell if a PCI device structure has a matching PCI device id structure
* @drv: the PCI driver to match against
* @dev: the PCI device structure to match against
*
* Used by a driver to check whether a PCI device present in the
* system is in its list of supported devices.  Returns the matching
* pci_device_id structure or %NULL if there is no match.
*/
const struct pci_device_id *pci_match_device(struct pci_driver *drv,
                                             struct pci_dev *dev)
{
        struct pci_dynid *dynid;
        /* Look at the dynamic ids first, before the static ones */
        spin_lock(&drv->dynids.lock);
        list_for_each_entry(dynid, &drv->dynids.list, node) {
                if (pci_match_one_device(&dynid->id, dev)) {
                        spin_unlock(&drv->dynids.lock);
                        return &dynid->id;
                }
        }
        spin_unlock(&drv->dynids.lock);
        return pci_match_id(drv->id_table, dev);
}
        pci_match_one_driver函数的作用是将一个PCI设备与PCI驱动进行比较,以查看它们是否相匹配。如果相匹配,则返回匹配的pci_device_id结构体指针。
此时,如果该PCI驱动已经找到了一个可以想符的PCI设备,则返回,然后再退回到之前的driver_probe_device函数中。在该函数最后将
调用really_probe函数。将device_driver与device结构体指针作为参数传递到这个函数中。下面几行是调用驱动或者总线的
probe函数来扫描设备。
pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->really_probe()
        在函数really_probe()中:
        if (dev->bus->probe) {
                ret = dev->bus->probe(dev);
                if (ret)
                        goto probe_failed;
        } else if (drv->probe) {
                ret = drv->probe(dev);
                if (ret)
                        goto probe_failed;
        }
        此时的dev->bus为pci_bus_type,其probe函数则对应为:pci_device_probe。
pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->really_probe()->pci_device_probe()
同样,在该函数中会获得当前的PCI设备的pci_dev结构体指针以及PCI驱动程序的pci_driver结构体指针。分别使用宏
to_pci_dev和to_pci_driver。最后则调用函数__pci_device_probe。在该函数中还会调用函数
pci_call_probe,这是最后的函数
pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->really_probe()->pci_device_probe()->__pci_device_probe()->pci_call_probe()
        在函数pci_call_probe里有一条语句:
static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,
                          const struct pci_device_id *id)
{
        int error;
/*  省略  */
        error = drv->probe(dev, id);
        在此处就调用了pci_driver的probe函数,对于这里的E100驱动来说,它的probe函数是最开始注册的e100_probe函数,在该函数中会完成对网卡设备net_device的初始化等操作。
       pci_register_driver()->__pci_register_driver()->driver_register()->bus_add_driver()->driver_attach()->__driver_attach()->driver_probe_device()->really_probe()->pci_device_probe()->__pci_device_probe()->pci_call_probe()->e100_probe()
        到这里,我们对网卡驱动的PCI层的初始化分析就告一个段落了,剩下的部分就是网卡驱动对网卡设备本身的初始化等操作。

scutan
回复于:2008-12-15 22:48:52

2.4 函数调用流程图
        在这里,为网卡在PCI层的注册画了一个函数调用的流程图,能够更直观地展现网卡从注册到调用其自身的网卡初始化的这一个函数调用过程。
[attach]226537[/attach]
[attach]226538[/attach]

1

2

scutan
回复于:2008-12-15 22:52:22

文笔实在不行,写了一个下午加晚上,才写这么点。希望能够对初学者有一点用吧。因为我最开始在看网卡驱动的时候,就是迷惑加载了网卡之后是如何调用到该网卡的probe函数的。所以就仔细地看了一下里面的源码。
这里主要还是起一个梳理的作用,很多代码也没有进一步地深入分析。不过对于网络架构来说,首先将整个调用的流程掌握了,对后面的理解也就更加方便了。
:lol:

Godbach
回复于:2008-12-15 22:54:50

scutan兄又有好文章了,明天仔细拜读啊。

albcamus
回复于:2008-12-15 23:10:29

补充一下,贴一下open函数的调用的笔记:
86, ifconfig eth0 up 会导致 net_device->open被调用,内幕!
       
            # strace ifconfig eth0 up 2>&1 |less -N
   
    可以看到,它是先用sockfd = socket(AF_INET, SOCK_DGRAM, 0)生成一个sockfd文件描述符,
    再ioctl(sockfd, SIOCSIFFLAGS, 加上IFF_UP标志)。 这样就导致了open方法的调用。
    socket文件描述符都是用socket(2)系统调用生成的:
           
        sys_socket() > sock_map_fd() > sock_attach_fd() :
                dentry->d_op = &sockfs_dentry_operations;
                ...
                init_file(file, sock_mnt, dentry, FMODE_READ|FMODE_WRITE, &socket_file_ops);
                SOCK_INODE(sock)->i_fop = &socket_file_ops;
       
        (回忆一下,这个是不是就类似于ext3_iget()里头对inode->i_fop的赋值?)
                static const struct file_operations socket_file_ops = {
                        .owner =        THIS_MODULE,
                        .llseek =        no_llseek,
                        .aio_read =        sock_aio_read,
                        .aio_write =        sock_aio_write,
                        .poll =                sock_poll,
                        .unlocked_ioctl = sock_ioctl,
                #ifdef CONFIG_COMPAT
                        .compat_ioctl = compat_sock_ioctl,
                #endif
                        .mmap =                sock_mmap,
                        .open =                sock_no_open,        /* special open code to disallow open via /proc */
                        .release =        sock_close,
                        .fasync =        sock_fasync,
                        .sendpage =        sock_sendpage,
                        .splice_write = generic_splice_sendpage,
                        .splice_read =        sock_splice_read,
                }
        其unlocked_ioctl = sock_ioctl,那么我们沿着sock_ioctl走下去:
               
                sock_ioctl() --switch到了default--> dev_ioctl() > --SIOCSIFFLAGS--> dev_ifsioc()
                > dev_change_flags() :
                       
                        if ((old_flags ^ flags) & IFF_UP) {IFF_UP/* Bit is different  ? */
                                ret = ((old_flags & IFF_UP) ? dev_close : dev_open)(dev);
        如果是设置了IFF_UP,就调用dev_open;如果是清除了IFF_UP,就调用dev_close。 看dev_open里的:
               
                ret = dev->open(dev);
       
        就在此时,struct net_device的open方法被调用。

albcamus
回复于:2008-12-15 23:11:23

2). remove函数何时被调用?
            
            当pci_dev消失时(设备被拔出),或者module被rmmod时。
            pci_unregister_driver() > driver_unregister() > driver_detach() > __device_release_driver():
                if (dev->bus && dev->bus->remove)
                        dev->bus->remove(dev);
                else if (drv->remove)
                        drv->remove(dev);
          
            对pci设备来说,这里的dev->bus就是&pci_bus_type,参考1)中的定义,我们知道其remove函数是
            pci_device_remove():
                        struct pci_dev * pci_dev = to_pci_dev(dev);
                        struct pci_driver * drv = pci_dev->driver;
                        if (drv) {
                                if (drv->remove)
                                        drv->remove(pci_dev);
                                pci_dev->driver = NULL;
                        }

albcamus
回复于:2008-12-15 23:11:50

[FYI] 增加/删除一个PCI device时的情景。
              (只有boot时的enumeration和hotplug两种情况可能导致设备出现与消失)
              pci device的发现:
                      [ pci_scan_slot() > pci_scan_single_device() > pci_scan_device()
                                                             > pci_device_add() ]
                pci_bus_add_devices() > pci_bus_add_device() > device_add() > bus_attach_device() :
                       
                        int device_attach(struct device *dev)
                        {
                                int ret = 0;
                                down(&dev->sem);
                                if (dev->driver) {
                                        ret = device_bind_driver(dev);
                                        if (ret == 0)
                                                ret = 1;
                                        else {
                                                dev->driver = NULL;
                                                ret = 0;
                                        }
                                } else {
                                        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
                                }
                                up(&dev->sem);
                                return ret;
                        }
                也就是说,如果已经有了dev->driver这个值,那么就直接bind上去;如果没有,那么:
                       
                        bus_for_each_drv() > __device_attach() > driver_probe_device() > really_probe()
                此后发生的情形就和从pci_register_driver()一直调用到really_probe()的一样了。
        remove:
        =======
                pci_remove_bus_device() > pci_destroy_dev() > pci_stop_dev() > device_unregister() >
                device_del() > bus_remove_device() > device_release_driver() > __device_release_driver() :
                        static void __device_release_driver(struct device *dev)
                        {
                                struct device_driver *drv;
                                drv = dev->driver;
                                if (drv) {
                                        driver_sysfs_remove(dev);
                                        sysfs_remove_link(&dev->kobj, "driver");
                                        if (dev->bus)
                                                blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                                                                             BUS_NOTIFY_UNBIND_DRIVER,
                                                                             dev);
                                        if (dev->bus && dev->bus->remove)
                                                dev->bus->remove(dev);
                                        else if (drv->remove)
                                                drv->remove(dev);
                                        devres_release_all(dev);
                                        dev->driver = NULL;
                                        klist_remove(&dev->knode_driver);
                                }
                        }
                注意,如果可以,首先尝试调用pci_bus_type的remove方法(a.k.a pci_device_remove),否则调用
                device_driver的remove方法。

scutan
回复于:2008-12-15 23:27:56

:em02:
谢谢albcamus版主精彩的补充。

kns1024wh
回复于:2008-12-16 08:48:27

实践的结论是最有说服力的

dreamice
回复于:2008-12-16 09:51:09

这贴牛,高手汇集。受益匪浅:lol:

Godbach
回复于:2008-12-16 10:04:16

是啊。先读一遍,然后再向scutan和albcamus兄请教。

Godbach
回复于:2008-12-16 10:56:10

引用:补充一下,贴一下open函数的调用的笔记:
大家要学习albcamus版主看书做笔记的好习惯啊。偶以前就没这个习惯,理解的东西没过多久就又忘了。

Arthur_
回复于:2008-12-16 13:25:12

五体投地:shock:

scutan
回复于:2008-12-16 13:38:57

谢谢各位。:em02:

Godbach
回复于:2008-12-16 14:09:43

通读一遍,写的很好。流程比较明确。
整个流程应该是使用了两个probe函数。一个是pci_bus_type的probe函数指针,指向pci_device_probe。
另一个就是驱动程序自己的probe函数,指向e100_probe,该函数在一下代码中调用。完成网卡的具体初始化工作。
引用:static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,
                          const struct pci_device_id *id)
{
        int error;
/*  省略  */
        error = drv->probe(dev, id);


Godbach
回复于:2008-12-16 14:16:41

不同的总线注册不同的struct bus_type结构体。随后在同一总线上注册多个设备驱动的时候,它们对应同一个struct bus_type。

Godbach
回复于:2008-12-16 14:33:49

一个小问题:
从内核代码中看到USB设备时使用usb_register注册USB设备,同时使用usb自身的bus_type。那这意味着USB总线是和PCI总线属于两种不同的总线。
但为什么在pci_ids.h中,有这样的定义:
引用:#define PCI_BASE_CLASS_SERIAL                0x0c
#define PCI_CLASS_SERIAL_FIREWIRE        0x0c00
#define PCI_CLASS_SERIAL_ACCESS                0x0c01
#define PCI_CLASS_SERIAL_SSA                0x0c02
#define PCI_CLASS_SERIAL_USB                0x0c03
#define PCI_CLASS_SERIAL_USB_UHCI        0x0c0300
#define PCI_CLASS_SERIAL_USB_OHCI        0x0c0310
#define PCI_CLASS_SERIAL_USB_EHCI        0x0c0320
#define PCI_CLASS_SERIAL_FIBER                0x0c04
#define PCI_CLASS_SERIAL_SMBUS                0x0c05

MS在PCI中有USB类的设备。这个该怎么理解。偶的硬件学得不好,熟悉的朋友能否帮忙解释一下?

Godbach
回复于:2008-12-16 16:44:36

LZ说明一下内核版本号吧。我这2.6.18.3,dd.c中没有really_probe函数。:wink:

scutan
回复于:2008-12-16 16:55:24

引用:原帖由 Godbach 于 2008-12-16 16:44 发表

LZ说明一下内核版本号吧。我这2.6.18.3,dd.c中没有really_probe函数。:wink:

我的是2.6.21

[url=http://linux.chinaunix.net/bbs/viewpro.php?uid=551201]scutan

回复于:2008-12-16 16:56:34

引用:原帖由 Godbach 于 2008-12-16 16:44 发表

LZ说明一下内核版本号吧。我这2.6.18.3,dd.c中没有really_probe函数。:wink:

driver_probe_device 函数
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
        struct stupid_thread_structure *data;
        struct task_struct *probe_task;
        int ret = 0;
        if (!device_is_registered(dev))
                return -ENODEV;
        if (drv->bus->match && !drv->bus->match(dev, drv))
                goto done;
        pr_debug("%s: Matched Device %s with Driver %s\n",
                 drv->bus->name, dev->bus_id, drv->name);
        data = kmalloc(sizeof(*data), GFP_KERNEL);
        if (!data)
                return -ENOMEM;
        data->drv = drv;
        data->dev = dev;
        if (drv->multithread_probe) {
                probe_task = kthread_run(really_probe, data,
                                         "probe-%s", dev->bus_id);
                if (IS_ERR(probe_task))
                        ret = really_probe(data);
        } else
                ret = really_probe(data);
done:
        return ret;
}

[url=http://linux.chinaunix.net/bbs/viewpro.php?uid=534931]Godbach

回复于:2008-12-16 17:00:51

2.6.18.3中dd.c.
函数driver_probe_device代码中dev->bus->probe对应的函数为pci_device_probe,随后调用关系为:->__pci_device_probe->pci_call_probe
该函数里面执行了 drv->probe,即e100驱动注册的e100_probe()
driver_probe_device函数的实现:
引用:int driver_probe_device(struct device_driver * drv, struct device * dev)
{
        int ret = 0;
        if (drv->bus->match && !drv->bus->match(dev, drv))
                goto Done;
        pr_debug("%s: Matched Device %s with Driver %s\n",
                 drv->bus->name, dev->bus_id, drv->name);
        dev->driver = drv;
        if (dev->bus->probe) {
                ret = dev->bus->probe(dev);
                if (ret) {
                        dev->driver = NULL;
                        goto ProbeFailed;
                }
        } else if (drv->probe) {
                ret = drv->probe(dev);
                if (ret) {
                        dev->driver = NULL;
                        goto ProbeFailed;
                }
        }
        device_bind_driver(dev);
        ret = 1;
        pr_debug("%s: Bound Device %s to Driver %s\n",
                 drv->bus->name, dev->bus_id, drv->name);
        goto Done;
ProbeFailed:
        if (ret == -ENODEV || ret == -ENXIO) {
                /* Driver matched, but didn't support device
                 * or device not found.
                 * Not an error; keep going.
                 */
                ret = 0;
        } else {
                /* driver matched but the probe failed */
                printk(KERN_WARNING
                       "%s: probe of %s failed with error %d\n",
                       drv->name, dev->bus_id, ret);
        }
Done:
        return ret;
}

pci_call_probe函数的实现
引用:static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,
                          const struct pci_device_id *id)
{
        int error;
#ifdef CONFIG_NUMA
        /* Execute driver initialization on node where the
           device's bus is attached to.  This way the driver likely
           allocates its local memory on the right node without
           any need to change it. */
        struct mempolicy *oldpol;
        cpumask_t oldmask = current->cpus_allowed;
        int node = pcibus_to_node(dev->bus);
        if (node >= 0 && node_online(node))
            set_cpus_allowed(current, node_to_cpumask(node));
        /* And set default memory allocation policy */
        oldpol = current->mempolicy;
        current->mempolicy = &default_policy;
        mpol_get(current->mempolicy);
#endif
        error = drv->probe(dev, id);
#ifdef CONFIG_NUMA
        set_cpus_allowed(current, oldmask);
        mpol_free(current->mempolicy);
        current->mempolicy = oldpol;
#endif
        return error;
}

[ 本帖最后由 Godbach 于 2008-12-16 17:04 编辑 ]

Godbach
回复于:2008-12-16 17:07:01

代码是变化了。应该是2.6.21的代码里面把driver_probe_device()函数的实现进行了封装。
[ 本帖最后由 Godbach 于 2008-12-16 17:09 编辑 ]

qinjiana0786
回复于:2008-12-16 19:16:57

分析的过程是正确的,但是在内核的PCI的过程上要是展开下就很好,不要只是用图片或者函数的调用路线来形容,毕竟内核的部分不是能用只
言片语就能一笔带过的,核心部分还是围绕着PCI的过程详细一下就太精彩了,如果LZ没有时间将来我会把这部分完成。只是现在还在进行TCP协议内容,而
且我看了一下其他人对这些内容的补充,实话说,在点但不在谱,就是说太零散,不利用于群内大多数朋友的学习,有点哗众取宠的感觉,这又不是问题贴没有必要
展开某个细节来说明,况且展开的也不是难点所在,没有主线,就象一个乐谱,每个人都做做自己的曲子插进来,这样的音乐能听吗?希望各位发扬自主写作的精
神,而不是靠讨论贴来完成一篇精华的文章。


Godbach
回复于:2008-12-16 20:51:39

非常欢迎qinjiana0786兄来完成更详细的分析啊。
LZ在这篇文章的开篇也说了,这里整理出来一个流程,提供一个主线,帮着梳理一下。这样可以帮助希望自己熟悉和分析代码,但又不知道如何下手的朋友。

eexplorer
回复于:2008-12-17 09:19:49

引用:原帖由 Godbach 于 2008-12-16 14:33 发表

一个小问题:
从内核代码中看到USB设备时使用usb_register注册USB设备,同时使用usb自身的bus_type。那这意味着USB总线是和PCI总线属于两种不同的总线。
但为什么在pci_ids.h中,有这样的定义:
MS在PCI ...

这些是usb host controller,是一个pci device(可通过lspci找到), 和usb host controller 通信的usb devices才是挂在usb bus上的

[url=http://linux.chinaunix.net/bbs/viewpro.php?uid=534931]Godbach

回复于:2008-12-17 09:42:01

引用:原帖由 eexplorer 于 2008-12-17 09:19 发表

这些是usb host controller,是一个pci device(可通过lspci找到), 和usb host controller 通信的usb devices才是挂在usb bus上的

多谢LS。那应该还有一些USB Device是不同USB host controller通信的,这样的设备是怎么工作的呢?

[url=http://linux.chinaunix.net/bbs/viewpro.php?uid=686144]bbaqq222

回复于:2008-12-17 09:58:09

感谢高人的讲解,认真琢磨一下

scutan
回复于:2008-12-17 13:40:13

引用:原帖由 qinjiana0786 于 2008-12-16 19:16 发表

分析的过程是正确的,但是在内核的PCI的过程上要是展开下就很好,不要只是用图片或者函数的调用路线来形容,毕竟内核的部分不是能用只言片语就能一笔带过的,核心部分还是围绕着PCI的过程详细一下就太精彩了,如 ...

小弟在硬件这块目前还没有精力去深入下去,所以希望qinjiana0786兄能够多多指点一下。
谢谢。
BTW: 我从您的TCP/IP核心过程分析中学到了很多东西,表示感谢!

[url=http://linux.chinaunix.net/bbs/viewpro.php?uid=256598]eexplorer

回复于:2008-12-17 17:33:05

引用:原帖由 Godbach 于 2008-12-17 09:42 发表

多谢LS。那应该还有一些USB Device是不同USB host controller通信的,这样的设备是怎么工作的呢?

不太明白你的问题,USB device应该只有通过USB host controller才能和CPU交互,

[url=http://linux.chinaunix.net/bbs/viewpro.php?uid=534931]Godbach

回复于:2008-12-17 17:49:10

引用:和usb host controller 通信的usb devices才是挂在usb bus上的
对这句话我的理解是,可能还有一些不通过usb host controller 通信的usb devices。
看来我的理解能力很弱

ller
回复于:2008-12-18 22:55:42

网卡驱动模块是什么时候加载的,是根据udev规则吗,我没有找到相应规则,还是起网络服务时加载的,我在运行级别1上仍然可以lsmod到我的网卡驱动模块

ller
回复于:2008-12-18 23:38:30

在网终的启动脚本上看到了对ethN的modprobe操作,然后根据modprobe.conf中的alias找到相应的驱动

albcamus
回复于:2008-12-19 12:56:53

引用:原帖由 qinjiana0786 于 2008-12-16 19:16 发表

分析的过程是正确的,但是在内核的PCI的过程上要是展开下就很好,不要只是用图片或者函数的调用路线来形容,毕竟内核的部分不是能用只言片语就能一笔带过的,核心部分还是围绕着PCI的过程详细一下就太精彩了,如 ...

linux有的看、有的用吗? 每个人就写了那么一点, 拼凑起来的:outu: :outu:

[url=http://linux.chinaunix.net/bbs/viewpro.php?uid=784896]OraBSD

回复于:2008-12-20 23:59:05

:shock:

emmoblin
回复于:2008-12-21 21:19:04

好贴啊,我也是不清楚怎么调用到probe的。
努力以后我也能写几篇加精的文章。努力。。。。

Godbach
回复于:2008-12-21 22:22:16

引用:原帖由 emmoblin 于 2008-12-21 21:19 发表

好贴啊,我也是不清楚怎么调用到probe的。
努力以后我也能写几篇加精的文章。努力。。。。

期待emmoblin兄多发几篇精品文章上来啊。

[url=http://linux.chinaunix.net/bbs/viewpro.php?uid=182323]hb12112

回复于:2008-12-23 15:46:59

精彩!

efr6
回复于:2008-12-23 18:42:42

thank you!

WFCJZ
回复于:2008-12-24 01:01:56

搞懂了,整死电信,叫它给人玩网卡绑定这招,最烦人!:lol:
[flash]http://eblog.cersp.com/UploadFiles/2007-5/59797326.swf [/flash]

ops
回复于:2008-12-24 12:44:41

好东西

wylhistory
回复于:2009-01-14 20:09:13

学习了!

原文链接:
http://linux.chinaunix.net/bbs/viewthread.php?tid=1052717
转载请注明作者名及原文出处
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/83200/showart_1799736.html