link layer transmit

一、ixgbe驱动

1、数据结构

在pci_driver中包含一个名为*id_table的pci_device_id结构,用于标识该驱动所能支持的网卡类型,这里ixgbe所能支持的相应结构为:   

 */
static DEFINE_PCI_DEVICE_TABLE(ixgbe_pci_tbl) = {
{PCI_VDEVICE(INTEL, IXGBE_DEV_ID_82598), 0},
{PCI_VDEVICE(INTEL, IXGBE_DEV_ID_82598AF_DUAL_PORT), 0},
{PCI_VDEVICE(INTEL, IXGBE_DEV_ID_82598AF_SINGLE_PORT), 0},
{PCI_VDEVICE(INTEL, IXGBE_DEV_ID_82598AT), 0},

        .....

/* required last entry */
{ .device = 0 }

}

 ixgbe驱动定义了pci_driver类型的结构——ixgbe_driver,并通过对其修改设置了该驱动的名称、其支持的网卡型号、以及对该网卡进行各种操作时候应该调用的相应函数句柄,代码如下:
static struct pci_driver ixgbe_driver = {
	.name     = ixgbe_driver_name,
	.id_table = ixgbe_pci_tbl,
	.probe    = ixgbe_probe,
	.remove   = __devexit_p(ixgbe_remove),
#ifdef CONFIG_PM
	.suspend  = ixgbe_suspend,
	.resume   = ixgbe_resume,
#endif
#ifndef USE_REBOOT_NOTIFIER
	.shutdown = ixgbe_shutdown,
#endif
#ifdef HAVE_SRIOV_CONFIGURE
	.sriov_configure = ixgbe_pci_sriov_configure,
#endif
#ifdef HAVE_PCI_ERS
	.err_handler = &ixgbe_err_handler
#endif
};

当PCI层检测到一个PCI设备能够被某PCI驱动所支持时(这是通过函数pci_match_one_device来进行检测的),就会调用这个PCI驱动上的probe函数,在该函数中会对该特定的PCI设备进行一些具体的初始化等操作。比如对于ixgbe设备驱动来说,其probe函数为ixgbe_probe,其主要作用是网卡设备net_device的初始化。而在网卡设备的初始化之前,首先需要完成的是将网卡设备注册到PCI层,即后文所表述的内容。

2. 驱动的初始化ixgbe_init() 
    Linux-2.6下的驱动有统一的入口和出口,即:module_init()和module_exit()。在ixgbe中,驱动的加载和注销的调用分别为:   
    module_init(ixgbe_init_module); 
    module_exit(ixgbe_exit_module);
    其中的ixgbe_init_module()和ixgbe_exit_module()分别为用于加载和注销ixgbe驱动的真正起作用的代码,其中引起网卡在PCI层注册的函数为ixgbe_init_module(),如下: 

/**
 * ixgbe_init_module - Driver Registration Routine
 *
 * ixgbe_init_module is the first routine called when the driver is
 * loaded. All it does is register with the PCI subsystem.
 **/
static int __init ixgbe_init_module(void)
{
	int ret;
	pr_info("%s - version %s\n", ixgbe_driver_string, ixgbe_driver_version);
	pr_info("%s\n", ixgbe_copyright);

	ixgbe_wq = create_singlethread_workqueue(ixgbe_driver_name);
	if (!ixgbe_wq) {
		pr_err("%s: Failed to create workqueue\n", ixgbe_driver_name);
		return -ENOMEM;
	}

#ifdef IXGBE_PROCFS
	if (ixgbe_procfs_topdir_init())
		pr_info("Procfs failed to initialize topdir\n");
#endif

#ifdef HAVE_IXGBE_DEBUG_FS
	ixgbe_dbg_init();
#endif /* HAVE_IXGBE_DEBUG_FS */
#if IS_ENABLED(CONFIG_DCA)
	dca_register_notify(&dca_notifier);
#endif

	ret = pci_register_driver(&ixgbe_driver);
	return ret;
}

    该函数中调用了pci_register_driver()来注册PCI驱动。 
3. 网卡驱动在PCI层的注册过程

    PCI的注册就是将PCI驱动程序挂载到其所在的总线的drivers链,同时扫描PCI设备,将它能够进行驱动的设备挂载到driver上的devices链表上。

    系统启动的时候,pci扫描所有的PCI设备然后根据注册驱动的id_table,找到相匹配的驱动,实现关联。当找到匹配的驱动时,它会执行相关驱动程序中的probe函数,而网卡的net_device就是在这个函数里面初始化的并注册到内核的。
 
 
3.1 pci_register_driver() 

    从前面已经得知,ixgbe_init_module()通过调用pci_register_driver()来对网卡驱动进行注册。而通过阅读内核代码可知,pci_register_driver并非一个函数,而是一个宏,它通过 __pci_register_driver()来完成驱动的注册。

/*
 * pci_register_driver must be a macro so that KBUILD_MODNAME can be expanded
 */
#define pci_register_driver(driver)		\
	__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

    __pci_register_driver()首先通过传入的参数struct pci_driver *ixgbe_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)
{
	/* initialize common driver fields */
	drv->driver.name = drv->name;
	drv->driver.bus = &pci_bus_type;
	drv->driver.owner = owner;
	drv->driver.mod_name = mod_name;

	spin_lock_init(&drv->dynids.lock);
	INIT_LIST_HEAD(&drv->dynids.list);

	/* register with core */
	return driver_register(&drv->driver);
}

    前面填充了drv->driver结构,然后调用spin_lock_init来初始化自旋锁,调用INIT_LIST_HEAD来初始化一个双向链表, 可见,这些初始化工作主要是设置了结构中所包含的device_driver这个结构。

    pci_driver中包含了一个通用的device_driver结构用于链接到pci_bus_type中的驱动链上。这里在对其初始化中,设置了device_driver的驱动名称、总线类型等信息。 其中的总线类型这里设置成了pci_bus_type. 初始化完成后 _pci_register_driver()将这个device_driver类型的变量作为参数传递给driver_register()以便将这个device_driver注册到系统中去。 

3.2 driver_register()

    该函数首先对入参检查,检测总线的操作函数和驱动的操作函数是否同时存在,同时存在则提示使用总线提供的操作函数。随后,函数根据入参检查该device所在的总线中是否已经有了同名的驱动,如果有则报错退出;如果没有则进行下一步,将这个驱动添加驱动到总线上 。

/**
 * 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.
 */
int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	BUG_ON(!drv->bus->p);

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

	other = driver_find(drv->name, drv->bus);
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	ret = bus_add_driver(drv);
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}
EXPORT_SYMBOL_GPL(driver_register);
     其中,driver_find()用于遍历已经在该bus上注册了的驱动,并按照驱动的名称查找相同名字的驱动,如果找到了,则返回该驱动的指针,否则返回NULL。按照driver_register的处理方式,这里如果找到了同名的驱动,则报错,返回-EEXIST并退出;否则,则调用bus_add_driver()来将该device_driver注册到bus上。 

3.3  bus_add_driver()
     bus_add_driver用于将一个device_drvier类型的驱动添加到相应的bus上去。该函数首先从入参中获取bus类型,然后分配驱动的私有数据,并对该私有数据进行初始化。私有数据的初始化包括初始化klist_devices列表,设置device_driver和私有数据(priv)的互相关联,设置私有数据的父容器。随后,又初始化kobj对象,设置容器操作集并建立相应的目录。在这些操作完成之后,bus_add_driver()调用driver_attach()来将驱动和设备进行绑定。 
     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);
}

     从中可以看出,driver_attach()利用bus_for_each_dev函数,扫描在drv->bus这个总线上的所有设备,然后将每个设备以及当前驱动这两个指针传递给__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 (!driver_match_device(drv, dev))
		return 0;

	if (dev->parent)	/* Needed for USB */
		device_lock(dev->parent);
	device_lock(dev);
	if (!dev->driver)
		driver_probe_device(drv, dev);
	device_unlock(dev);
	if (dev->parent)
		device_unlock(dev->parent);

	return 0;
}

    __driver_attach()调用drv->bus->match()来将device和driver进行匹配。由于前面ixgbe_init_module() 中调用pci_register_driver的时候,将device_driver中的bus设置成了pci_bus_type,而pci_bus_type中相应的match函数为:pci_bus_match,其原型为:static int pci_bus_match(struct device *dev, struct device_driver *drv)。该函数首先将入参中的device类型的dev和device_driver类型的drv转换成了pci_dev和pci_driver类型,随后调用pci_match_device检查pci_dev中记录的设备信息是否与驱动中的id_table中记录的相符,如果没有,则证明PCI总线上没有该驱动可以支持的设备,返回0并退出;而如果找到了该驱动支持的设备,则返回该设备的pci_device_id,并进行下一步操作—— 将设备与驱动进行绑定(driver_probe_device)。 
    driver_probe_device首先对入参进行了一些例行的检查,如该设备是否已经注册、设备和驱动是否匹配等等。随后调用real_probe()进行真正的操作。

static int really_probe(struct device *dev, struct device_driver *drv)
{
	int ret = 0;

	atomic_inc(&probe_count);
	pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
		 drv->bus->name, __func__, drv->name, dev_name(dev));
	WARN_ON(!list_empty(&dev->devres_head));

	dev->driver = drv;

	/* If using pinctrl, bind pins now before probing */
	ret = pinctrl_bind_pins(dev);
	if (ret)
		goto probe_failed;

	if (driver_sysfs_add(dev)) {
		printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
			__func__, dev_name(dev));
		goto probe_failed;
	}

	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;
	}

	driver_bound(dev);
	ret = 1;
	pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);
	goto done;

probe_failed:
	devres_release_all(dev);
	driver_sysfs_remove(dev);
	dev->driver = NULL;
	dev_set_drvdata(dev, NULL);

	if (ret == -EPROBE_DEFER) {
		/* Driver requested deferred probing */
		dev_info(dev, "Driver %s requests probe deferral\n", drv->name);
		driver_deferred_probe_add(dev);
	} else if (ret != -ENODEV && ret != -ENXIO) {
		/* driver matched but the probe failed */
		printk(KERN_WARNING
		       "%s: probe of %s failed with error %d\n",
		       drv->name, dev_name(dev), ret);
	} else {
		pr_debug("%s: probe of %s rejects match %d\n",
		       drv->name, dev_name(dev), ret);
	}
	/*
	 * Ignore errors returned by ->probe so that the next driver can try
	 * its luck.
	 */
	ret = 0;
done:
	atomic_dec(&probe_count);
	wake_up(&probe_waitqueue);
	return ret;
}

    其中,dev->driver = drv将传入的设备的驱动设置成了传入的驱动;driver_sysfs_add()在传入的驱动(*drv)的私有数据中建立了一个传入的设备的符号链接。这样将驱动和设备进行了绑定。 随后,由于此时的dev->bus为pci_bus_type,其probe函数则对应为:pci_device_probe,入参为struct device *dev。

static int pci_device_probe(struct device * dev)
{
	int error = 0;
	struct pci_driver *drv;
	struct pci_dev *pci_dev;

	drv = to_pci_driver(dev->driver);
	pci_dev = to_pci_dev(dev);
	pci_dev_get(pci_dev);
	error = __pci_device_probe(drv, pci_dev);
	if (error)
		pci_dev_put(pci_dev);

	return error;
}

    pci_device_probe()首先使用宏to_pci_dev和to_pci_driver来从入参中获取当前的PCI设备的pci_dev结构体指针以及PCI驱动程序的pci_driver结构体指针,并通过__pci_device_probe()函数中pci_call_probe()来调用PCI驱动里面的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, node;
	struct drv_dev_and_id ddi = { drv, dev, id };

	/* 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. */
	node = dev_to_node(&dev->dev);
	if (node >= 0) {
		int cpu;

		get_online_cpus();
		cpu = cpumask_any_and(cpumask_of_node(node), cpu_online_mask);
		if (cpu < nr_cpu_ids)
			error = work_on_cpu(cpu, local_pci_probe, &ddi);
		else
			error = local_pci_probe(&ddi);
		put_online_cpus();
	} else
		error = local_pci_probe(&ddi);
	return error;
}

    其中local_pci_probe调用了pci_drv->probe()来进行设备的探测和初始化工作,对于ixgbe驱动来讲,其drv即为ixgbe_driver,而相应的ixgbe_drvier->probe所指向的探测函数即为ixgbe_probe()。

    从这里开始,系统完成了ixgbe驱动的注册,将ixgbe驱动与网卡进行了绑定,并开始了初始化工作,包括igb_adapter以及操作系统相关的初始化等工作。 

    等igb_probe()完成之后,bus_add_driver() 还要进行一些最后的收尾工作,如填充驱动的私有数据、在/sys中创建与该驱动相关的文件等等。这些工作完成之后,IGB的驱动才算真正注册到了系统,该设备才算真正注册到了PCI层。 

二、状态通知

  网络设备在系统中注册、注销和关闭、打开等事件都可以通知给相应的内核组件或用户空间应用程序,其中内核组件通过netdev_chain通知链获取消息,而用户空间应用程序则通过注册Netlink RTMGRP_LINK多播群组获取事件消息。

A、netdev_chain:内核组件可以注册此通知链。

B、netlink的RTMGRP_LINK多播群组:用户空间应用程序可以netlink的RTMGRP_LINK多播群组

1、netdev_chain

    可以通过register_netdev_notifier和unregister_netdev_notifier分别对该链注册或除名,在网络设备相应的事件发生时,会调用这个通知链通知这些内核组件所有由netdev_chain报告的NETDEV_XXX事件都在include/linux/notifier.h中。以下是几种事件以及触发这些事件的条件:

  • NETDEV_UP  //送出NETDEV_UP以报告设备开启, 而且此事件是有dev_open产生。
  • NETDEV_GOING_DOWN  //当设备要关闭时,就会送出NETDEV_GOING_DOWN。
  • NETDEV_DOWN  //当设备已关闭时,就会送出,NETDEV_DOWN。这些事件都是由dev_close产生的。
  • NETDEV_REGISTER  //设备已注册,此事件是由register_netdevice产生的。
  • NETDEV_UNREGISTER  //设备已经除名。此事件是由unregister_netdevice产生的。
  • NETDEV_REBOOT  //因为硬件失败,设备已重启。目前没有用
  • NETDEV_CHANGEADDR  //设备的硬件地址(或相关联的广播地址)已改变。
  • NETDEV_CHANGENAME  //设备已改变其名称。
  • NETDEV_CHANGE //设备的状态或配置改变。会用在NETDEV_CHANGEADDR和NETDEV_CHANGENAME没包括在内的所有情况下。
2、 网卡链路状态检测

   网卡在物理上具有载波侦听的功能,当网络连接完整或者网络链接断开时,网卡芯片硬件会自动设置寄存器标志位来标识。如网线链接断开的时候,会将LinkSts清位;重新链接网线,则硬件自动将此位置位。这样,在网卡驱动中读写该位信息就可一判断网络是否链接通路。网卡驱动程序通过netif_carrier_on/netif_carrier_off/netif_carrier_ok来和内核网络子系统传递信息,由rtmsg_ifinfo把通知信息传递给link多播群组RTMRGP_LINK。

1】netif_carrier_on:【作用】告诉内核子系统网络链接完整。

2】netif_carrier_off:【作用】告诉内核子系统网络断开。

3】netif_carrier_ok: 【作用】查询网络断开还是链接。

以上函数主要是改变net_device dev的state状态来告知内核链路状态的变化。

/**
 *	netif_carrier_on - set carrier
 *	@dev: network device
 *
 * Device has detected that carrier.
 */
void netif_carrier_on(struct net_device *dev)
{
	if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
		if (dev->reg_state == NETREG_UNINITIALIZED)
			return;
		linkwatch_fire_event(dev);
		if (netif_running(dev))
			__netdev_watchdog_up(dev);
	}
}
EXPORT_SYMBOL(netif_carrier_on);

/**
 *	netif_carrier_off - clear carrier
 *	@dev: network device
 *
 * Device has detected loss of carrier.
 */
void netif_carrier_off(struct net_device *dev)
{
	if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
		if (dev->reg_state == NETREG_UNINITIALIZED)
			return;
		linkwatch_fire_event(dev);
	}
}
EXPORT_SYMBOL(netif_carrier_off);

这样,netif_carrier_on会调用linkwatch_fire_event,把事件加入到事件队列中:

	spin_lock_irqsave(&lweventlist_lock, flags);
	if (list_empty(&dev->link_watch_list)) {
		list_add_tail(&dev->link_watch_list, &lweventlist);
		dev_hold(dev);
	}
	spin_unlock_irqrestore(&lweventlist_lock, flags);


然后它调用linkwatch_schedule_work由内核线程去处理这些事件。它最终由linkwatch_run_queue(void)中的linkwatch_do_dev去完成这些处理工作:

static void linkwatch_do_dev(struct net_device *dev)
{
	/*
	 * Make sure the above read is complete since it can be
	 * rewritten as soon as we clear the bit below.
	 */
	smp_mb__before_clear_bit();

	/* We are about to handle this device,
	 * so new events can be accepted
	 */
	clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state);

	rfc2863_policy(dev);
	if (dev->flags & IFF_UP) {
		if (netif_carrier_ok(dev))
			dev_activate(dev);
		else
			dev_deactivate(dev);

		netdev_state_change(dev);
	}
	dev_put(dev);
}


可以看到,它的最主要工作之一就是netdev_state_change(dev):

void netdev_state_change(struct net_device *dev)
{
         if (dev->flags & IFF_UP) {
                 call_netdevice_notifiers(NETDEV_CHANGE, dev);
                 rtmsg_ifinfo(RTM_NEWLINK, dev, 0);
         }
 }
EXPORT_SYMBOL(netdev_state_change);

rtmsg_ifinfo把通知信息传递给link多播群组RTMRGP_LINK。

注意: 它只会在网卡状态为UP时,才会发出通知,因为,如果状态为DOWN,网卡链路的状态改变也没什么意义。

        

三、用户空间用户程序

    Netlink的RTMGRP_LINK多播群组,用户空间程序可以注册netlink的RTMGRP_LINK多播群组,当设备的状态或配置中有变更时,就会用rtmsg_ifinfo把通知信息传送给Link多播群组RTMGRP_LINK。netlink API用起来相对麻烦,可以考虑采用libnl开源库。

3.1 RTMGRP_LINK注册

1、nl_sock结构体

  libnl以面向对象的方式重新封装了netlink原有的API。其使用时必须分配一个nl_sock结构体。下面展示了和它相关的一些API及使用方法。

#include <netlink/socket.h>
    struct nl_sock *nl_socket_alloc(void)// 分配nl_sock结构体
    void nl_socket_free(struct nl_sock *sk)//释放nl_sock结构体

int nl_connect(struct nl_sock *sk, int protocol)// nl_connet内部将通过bind函数将netlink socket和protocol对应的模块进行绑定

2、设置消息处理函数

 linbl还可为每个nl_sock设置消息处理函数, 为nl_sock对象设置一个回调函数,当该socket上收到消息后,就会回调此函数进行处理。
    void nl_socket_set_cb(struct nl_sock *sk, struct nl_cb *cb);// 回调函数及参数封装在结构体struct nl_cb中
    struct nl_cb *nl_socket_get_cb(const struct nl_sock *sk);// 获取该nl_sock设置的回调函数信息

/*

  此API对消息接收及处理的力度更为精细,其中:
  type类型包括NL_CB_ACK、NL_CB_SEQ_CHECK、NL_CB_INVALID等,可用于处理底层不同netlink消息的情况。
  例如,当收到的netlink消息无效时,将调用NL_CB_INVALIDE设置的回调函数进行处理。
  nl_cb_kinds指定消息回调函数的类型,可选值有NL_CB_CUSTOM,代表用户设置的回调函数,NL_CB_DEFAULT 代表默认的处理函数。
  回调函数的返回值包括以下。
  NL_OK:表示处理正常。
  NL_SKIP:表示停止当前netlink消息分析,转而去分析接收buffer中下一条netlink消息(消息分
    片的情况)。
  NL_STOP:表示停止此次接收buffer中的消息分析。
*/
int nl_socket_modify_cb(struct nl_sock *sk,
                    enum nl_cb_type type, enum nl_cb_kind kind,
                    nl_recvmsg_msg_cb_t func, void *arg);


3、添加group:RTNLGRP_LINK

    nl_socket_add_membership(sk, RTNLGRP_LINK);

4、nl_sock结构体

   netlink还可设置错误消息(即专门处理nlmsgerr数据)处理回调函数,相关API如下。

#include <netlink/handlers.h>  // 必须包含此头文件
    int nl_cb_err(struct nl_cb *cb, enum nl_cb_kind kind,
                 nl_recvmsg_err_cb_t func, void * arg);// 设置错误消息处理
    typedef int(* nl_recvmsg_err_cb_t)(struct sockaddr_nl *nla,
                 struct nlmsgerr *nlerr, void *arg);

3.2、消息接收和发送

netlink直接利用系统调用(如send、recv、sendmsg、recvmsg等)进行数据收发,而libnl封装了自己特有的数据收发API。

其中和发送有关的几个主要API如下。
    int nl_sendto (struct nl_sock *sk, void *buf, size_t size);// 直接发送netlink消息
    int nl_send (struct nl_sock *sk, struct nl_msg *msg);// 发送nl_msg消息
    int nl_send_simple(struct nl_sock *sk, int type,int flags,void *buf, size_t size);

常用的数据接收API如下。
    // 核心接收函数。nla参数用于存储发送端的地址信息。creds用于存储权限相关的信息
    int nl_recv(struct nl_sock *sk, struct sockaddr_nl *nla,
               unsigned char **buf, struct ucred **creds)
    // 内部通过nl_recv接收消息,然后通过cb回调结构体中的回调函数传给接收者
    int nl_recvmsgs (struct nl_sock *sk, struct nl_cb *cb)

3.3、消息处理

libnl定义了自己的消息结构体struct nl_msg。不过它也提供API直接处理netlink的消息。常用的API如下。
    #include <netlink/msg.h>   // 必须包含这个头文件
    // 下面这两个函数计算netlink消息体中对应部分的长度
    int nlmsg_size(int payloadlen);  // 请参考图来理解这两个函数返回值的意义
    int nlmsg_total_size(int payloadlen);

关于netlink消息的长度如图3-51所示。

 

其他可直接处理netlink消息的API如下。
struct nlmsghdr *nlmsg_next(struct nlmsghdr *hdr, int *remaining);
int nlmsg_ok(const struct nlmsghdr *hdr, int remaining);
/*定义一个消息处理的for循环宏,其值等于
for (int rem = len, pos = head; nlmsg_ok(pos, rem);\
      pos = nlmsg_next(pos, &rem))
*/
#define nlmsg_for_each(pos,head,en)   


也可以通过libnl定义的消息结构体nl_msg进行相关操作,和nl_msg有关的API如下。

struct nlmsghdr *nlmsg_hdr(struct nl_msg *n);// Return actual netlink message




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值