`
Michaelmatrix
  • 浏览: 208027 次
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Linux内核网络设备操作部分阅读笔记

 
阅读更多

一、网络设备的初始化

网络设备初始化就是调用具有__init 前缀的net_dev_init函数完成的,网络设备初始化包含两个部分(在linux内核2.4办源代码分析大全一书的第550页有详细说明),就是:

在系统初始化期间对系统已知的网络设备进行初始化过程,也就是,我们在编译内核时选择编入内核的那部分网卡设备就会在这个时候逐个进行初始化工作。系统中已知的网络设备都存储在一个全局表中,dev_base[],它将所有网络设备的net_devive结构连接在一起。

int __init net_dev_init(void)

{

struct net_device *dev, **dp;

int i;

/*

这里如果是从系统初始化调用的则全局变量dev_boot_phase1,如果是模块插入则dev_boot_phase0,到达这个地方以后就直接返回了。这就保证了系统调用设备初始化函数一次。

*/

if (!dev_boot_phase)

return 0;

/*

在这里初始化数据包接收队列。

*/

for (i = 0; i < NR_CPUS; i++) {

struct softnet_data *queue;

/*

softnet_data数组,内核为每一个CPU都维护一个接收数据包的队列,这样不同的CPU之间就不需要进行互斥访问操作了。当然,如果只有一个CPU的话,这个队列的维数就是1

struct softnet_data{

int throttle; /* 1 表示当前队列的数据包被禁止*/

int cng_level; /*表示当前处理器的数据包处理拥塞程度*/

int avg_blog; /*某个处理器的平均拥塞度*/

struct sk_buff_head input_pkt_queue;/*接收缓冲区的sk_buff队列*/

struct list_head poll_list; /*POLL设备队列头*/

struct net_device output_queue; /*网络设备发送队列的队列头*/

struct sk_buff completion_queue; /*完成发送的数据包等待释放的队列*/

struct net_device backlog_dev; /*表示当前参与POLL处理的网络设备*/

};

在这里我们初始化网络接收数据报队列的相关参数,初始化输入包队列、完成队列等,其中应当注意的是咱这里注册了网卡的通用poll方法,该方法在网络软中断处理当中负责从数据包队列当中取出待处理数据,并递交给上层协议进行处理。每个网卡驱动程序可以自己实现poll函数,例如e1000就实现了poll方法,而一般的讲,是不需要自己实现该函数的,因为系统已经提供了一个默认函数process_backlog

*/

queue = &softnet_data[i];

skb_queue_head_init(&queue->input_pkt_queue);

queue->throttle = 0;

queue->cng_level = 0;

queue->avg_blog = 10; /* arbitrary non-zero */

queue->completion_queue = NULL;

INIT_LIST_HEAD(&queue->poll_list);

set_bit(__LINK_STATE_START, &queue->blog_dev.state);

queue->blog_dev.weight = weight_p;

queue->blog_dev.poll = process_backlog;

atomic_set(&queue->blog_dev.refcnt, 1);

}

/*

开始添加设备,这里从dev_base列表当中逐个取出net_device结构,如果该结构存在,则对结构的一些参数进行初始化。最为关键的就是在这里调用每个设备的init函数,如果该设备已经存在则init函数应该调用成功,如果调用失败,则表明可能该设备并不存在,应当从dev_base列表中清除。

对于每个设备的dev->init方法对于不同类型的网络设备各不相同,对于以太网卡来说,在driver/net/Space.c当中将其初始化为ethif_probe

*/

dp = &dev_base;

while ((dev = *dp) != NULL) {

spin_lock_init(&dev->queue_lock);

spin_lock_init(&dev->xmit_lock);

dev->xmit_lock_owner = -1;

dev->iflink = -1;

dev_hold(dev);

/*

* Allocate name. If the init() fails

* the name will be reissued correctly.

*/

if (strchr(dev->name, '%'))

dev_alloc_name(dev, dev->name);

/*

* Check boot time settings for the device.

*/

netdev_boot_setup_check(dev);

if (dev->init && dev->init(dev)) {

/*

Init函数调用成功返回0,否则返回非0,这里返回非0,表明初始化过程出现了问题,我们将它的deadbeaf标志置为,并在下面把这个设备从列表中删除。如果init成功,则进一步进行初始化。

*/

dev->deadbeaf = 1;

dp = &dev->next;

} else {

dp = &dev->next;

dev->ifindex = dev_new_index();

if (dev->iflink == -1)

dev->iflink = dev->ifindex;

if (dev->rebuild_header == NULL)

dev->rebuild_header = default_rebuild_header;

dev_init_scheduler(dev);

set_bit(__LINK_STATE_PRESENT, &dev->state);

}

}

/*

在这里根据deadbeaf标志讲所有init调用失败的设备删除。

*/

dp = &dev_base;

while ((dev = *dp) != NULL) {

if (dev->deadbeaf) {

write_lock_bh(&dev_base_lock);

*dp = dev->next;

write_unlock_bh(&dev_base_lock);

dev_put(dev);

} else {

dp = &dev->next;

}

}

/*

到这里内核初始化过程已经完成。

*/

dev_boot_phase = 0;

/*

注册网络接收和发送软中断处理函数,发送处理函数是net_tx_action,接收处理函数是net_rx_action。当系统调用软中断入口函数do_softirq时,就是调用net_rx_action函数对接收数据包队列进行处理的,在这个函数当中又调用了相应网络设备的poll方法,以轮询的方式遍历数据包队列,将数据报向上层协议转发。

*/

open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);

open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);

dst_init();

dev_mcast_init();

/*

* Initialise network devices

*/

net_device_init();

return 0;

<wrapblock>&lt;!--[if !vml]--&gt;<span> <table cellspacing="0" cellpadding="0"><tbody> <tr> <td width="59"></td> </tr> <tr> <td></td> <td></td> </tr> </tbody></table></span>&lt;!--[endif</wrapblock>
}

在这里我们列出了网络设备在内核初始化过程中的初始化函数调用关系。

上面对net_dev_init函数的分析已经知道,为每一个dev调用init函数,对于不同的设备系统在drivers/net/Space.c当中已经分别进行了注册,对于以太网卡则将其init方法注册为ethif_probe,它调用probe_list函数为每一个以太网卡调用已经注册好的probe方法。对于基于pci设备的网卡驱动程序来说,应该为每一个网卡创建一个pci_driver结构,该结构定义了该设备如何探测,如何从系统中删除等等基本的操作函数。每当实现一个新的网卡驱动程序时,就可以将相应的处理函数注册到该结构当中,调用点就在probe_list函数中。

对于e1000网卡驱动来说,它的probe函数注册为e1000_probe,因此这也就是这个网卡最早被调用的函数:

static int __devinit e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent)

{

struct net_device *netdev;

struct e1000_adapter *adapter;

static int cards_found = 0;

unsigned long mmio_start;

int mmio_len;

int pci_using_dac;

int i;

int err;

uint16_t eeprom_data;

if((err = pci_enable_device(pdev)))

return err;

/*

初始化网卡的DMA传输机制。

*/

if(!(err = pci_set_dma_mask(pdev, PCI_DMA_64BIT))) {

pci_using_dac = 1;

} else {

if((err = pci_set_dma_mask(pdev, PCI_DMA_32BIT))) {

E1000_ERR("No usable DMA configuration, aborting/n");

return err;

}

pci_using_dac = 0;

}

if((err = pci_request_regions(pdev, e1000_driver_name)))

return err;

pci_set_master(pdev);

/*

为这个设备分配一个net_device结构,并且调用ether_setup初始化该结构的部分成员,特别是所有以太网设备的共同参数。

*/

netdev = alloc_etherdev(sizeof(struct e1000_adapter));

if(!netdev) {

err = -ENOMEM;

goto err_alloc_etherdev;

}

SET_MODULE_OWNER(netdev);

pci_set_drvdata(pdev, netdev);

adapter = netdev->priv;

adapter->netdev = netdev;

adapter->pdev = pdev;

adapter->hw.back = adapter;

mmio_start = pci_resource_start(pdev, BAR_0);

mmio_len = pci_resource_len(pdev, BAR_0);

adapter->hw.hw_addr = ioremap(mmio_start, mmio_len);

if(!adapter->hw.hw_addr) {

err = -EIO;

goto err_ioremap;

}

for(i = BAR_1; i <= BAR_5; i++) {

if(pci_resource_len(pdev, i) == 0)

continue;

if(pci_resource_flags(pdev, i) & IORESOURCE_IO) {

adapter->hw.io_base = pci_resource_start(pdev, i);

break;

}

}

/*

e1000网卡驱动程序的各种处理函数注册到net_device结构的各个成员上,open方法用来打开一个网卡设备,hard_start_xmit方法用来进行网络数据传输,而poll方法就是在软中断处理程序net_rx_action中被调用的poll方法。并且调用ether_setup初始化该结构的部分成员,特别是所有以太网设备的共同参数。

*/

netdev->open = &e1000_open;

netdev->stop = &e1000_close;

netdev->hard_start_xmit = &e1000_xmit_frame;

netdev->get_stats = &e1000_get_stats;

netdev->set_multicast_list = &e1000_set_multi;

netdev->set_mac_address = &e1000_set_mac;

netdev->change_mtu = &e1000_change_mtu;

netdev->do_ioctl = &e1000_ioctl;

netdev->tx_timeout = &e1000_tx_timeout;

netdev->watchdog_timeo = 5 * HZ;

#ifdef CONFIG_E1000_NAPI

/*

如果在配置内核参数时选择了采用NAPI处理网络数据,则定义e1000自己的poll处理函数(e1000_clean),否则就采用系统默认的处理函数process_backlog。以后会对这两个函数的作用及不同点进行分析和比较。

*/

netdev->poll = &e1000_clean;

netdev->weight = 64;

#endif

netdev->vlan_rx_register = e1000_vlan_rx_register;

netdev->vlan_rx_add_vid = e1000_vlan_rx_add_vid;

netdev->vlan_rx_kill_vid = e1000_vlan_rx_kill_vid;

netdev->irq = pdev->irq;

netdev->mem_start = mmio_start;

netdev->mem_end = mmio_start + mmio_len;

netdev->base_addr = adapter->hw.io_base;

adapter->bd_number = cards_found;

/* setup the private structure */

if((err = e1000_sw_init(adapter)))

goto err_sw_init;

if(adapter->hw.mac_type >= e1000_82543) {

netdev->features = NETIF_F_SG |

NETIF_F_HW_CSUM |

NETIF_F_HW_VLAN_TX |

NETIF_F_HW_VLAN_RX |

NETIF_F_HW_VLAN_FILTER;

} else {

netdev->features = NETIF_F_SG;

}

#ifdef NETIF_F_TSO

if((adapter->hw.mac_type >= e1000_82544) &&

(adapter->hw.mac_type != e1000_82547))

netdev->features |= NETIF_F_TSO;

#endif

if(pci_using_dac)

netdev->features |= NETIF_F_HIGHDMA;

/* before reading the EEPROM, reset the controller to

* put the device in a known good starting state */

e1000_reset_hw(&adapter->hw);

/* make sure the EEPROM is good */

if(e1000_validate_eeprom_checksum(&adapter->hw) < 0) {

printk(KERN_ERR "The EEPROM Checksum Is Not Valid/n");

err = -EIO;

goto err_eeprom;

}

/* copy the MAC address out of the EEPROM */

e1000_read_mac_addr(&adapter->hw);

memcpy(netdev->dev_addr, adapter->hw.mac_addr, netdev->addr_len);

if(!is_valid_ether_addr(netdev->dev_addr)) {

err = -EIO;

goto err_eeprom;

}

e1000_read_part_num(&adapter->hw, &(adapter->part_num));

e1000_get_bus_info(&adapter->hw);

init_timer(&adapter->tx_fifo_stall_timer);

adapter->tx_fifo_stall_timer.function = &e1000_82547_tx_fifo_stall;

adapter->tx_fifo_stall_timer.data = (unsigned long) adapter;

init_timer(&adapter->watchdog_timer);

adapter->watchdog_timer.function = &e1000_watchdog;

adapter->watchdog_timer.data = (unsigned long) adapter;

init_timer(&adapter->phy_info_timer);

adapter->phy_info_timer.function = &e1000_update_phy_info;

adapter->phy_info_timer.data = (unsigned long) adapter;

INIT_TQUEUE(&adapter->tx_timeout_task,

(void (*)(void *))e1000_tx_timeout_task, netdev);

/*

将自己注册到dev_base数组当中,这个调用主要是为了模块启动准备的,因为如果该网卡在net_dev_init已经初始化,则dev_base数组当中已经存在,在register_netdevice函数中就会判断当前的net_device是否与dev_base中的某个相同,如果发现相同的就立刻返回。对于模块方式来说,此时正是该设备初始化的好时机,首先调用dev->init方法尝试对设备进行初始化工作,然后将自己添加到dev_base数组的最后,这样系统就可以利用这个设备收发数据了。

register_netdev事实上是重复了net_dev_init的很多工作

*/

register_netdev(netdev);

/* we're going to reset, so assume we have no link for now */

netif_carrier_off(netdev);

netif_stop_queue(netdev);

printk(KERN_INFO "%s: Intel(R) PRO/1000 Network Connection/n",

netdev->name);

e1000_check_options(adapter);

}

二、网络设备的打开和关闭操作

每一个pci设备的驱动程序在注册自己的时候都提供了open方法和close方法用来对设备进行打开和关闭操作。

设备的open操作在net/core/dev.c中的dev_open函数中调用。究竟dev_open是由谁调用的我们并不关心。

网卡设备是否处于打开状态由dev->flags标志控制,如果该标志置为IFF_UP,则表明设备已经打开。

三、网络数据的接收过程

网络数据的接收过程是根据硬件的中断来实现的,每个网卡驱动程序提供硬件中断处理函数,系统收到某个硬件发来的中断信号以后,调用do_IRQ函数。该函数根据中断信号中带的中断号判断是那一个硬件产生的中断,然后查找相应的设备的中断处理入口函数,调用handle_IRQ_event,在这个函数中调用了网卡的中断处理函数。以e1000为例,则是e1000_intr

static irqreturn_t e1000_intr(int irq, void *data, struct pt_regs *regs)

{

struct net_device *netdev = data;

struct e1000_adapter *adapter = netdev->priv;

uint32_t icr = E1000_READ_REG(&adapter->hw, ICR);

#ifndef CONFIG_E1000_NAPI

unsigned int i;

#endif

/*

首先判断是否是该网卡的中断号。

*/

if(!icr)

return IRQ_NONE; /* Not our interrupt */

if(icr & (E1000_ICR_RXSEQ | E1000_ICR_LSC)) {

adapter->hw.get_link_status = 1;

mod_timer(&adapter->watchdog_timer, jiffies);

}

/*

如果采用网卡的NAPI方法,则在这里转入轮询的处理过程。

*/

#ifdef CONFIG_E1000_NAPI

if(netif_rx_schedule_prep(netdev)) {

/* Disable interrupts and register for poll. The flush

of the posted write is intentionally left out.

*/

atomic_inc(&adapter->irq_sem);

E1000_WRITE_REG(&adapter->hw, IMC, ~0);

__netif_rx_schedule(netdev);

}

#else

/*

否则,采用正常的中断处理机制,这里设置一个变量E1000_MAX_INTR可能表示一次中断处理,网卡能够处理的网络数据包的个数。因为从效率的角度上讲,中断一次能够处理的数据包越多越好,但是中断处理函数需要屏蔽所有的中断,因此,过长的处理时间会导致系统不能同时相应其他的中断请求。

*/

for(i = 0; i < E1000_MAX_INTR; i++)

if(!e1000_clean_rx_irq(adapter) &

!e1000_clean_tx_irq(adapter))

break;

#endif

return IRQ_HANDLED;

}

事实上,在网卡的中断模式下,e1000_clean_rx_irq是从硬件接收数据的关键函数,同时,该函数还可以处理轮询情况。

e1000_clean_rx_irq(struct e1000_adapter *adapter)

{

i = rx_ring->next_to_clean;

rx_desc = E1000_RX_DESC(*rx_ring, i);

while(rx_desc->status & E1000_RXD_STAT_DD) {

buffer_info = &rx_ring->buffer_info[i];

#ifdef CONFIG_E1000_NAPI

if(*work_done >= work_to_do)

break;

(*work_done)++;

#endif

cleaned = TRUE;

/*

当前的PCI网卡驱动程序基本上都是采用DMA传输的方式接收数据包,一次硬件中断的产生表明一批数据已经通过设备的DMA通道从硬件层传递到了DMA缓冲区当中,这个DMA缓冲区是在设备创建的时候申请的。数据已经拷贝到DMA区以后,会产生一个中断,通知中断处理函数将这些数据取走。这里调用pci_unmap_single就是将缓冲区映射解除,以便开始处理数据。

*/

pci_unmap_single(pdev,

buffer_info->dma,

buffer_info->length,

PCI_DMA_FROMDEVICE);

skb = buffer_info->skb;

length = le16_to_cpu(rx_desc->length);

/*

这里忽略了错误处理。

*/

/* 接收到一个正确的数据包。*/

skb_put(skb, length - ETHERNET_FCS_SIZE);

/* 校验和 */

e1000_rx_checksum(adapter, rx_desc, skb);

skb->protocol = eth_type_trans(skb, netdev);

/*

采用NAPI方法接收数据包。

*/

#ifdef CONFIG_E1000_NAPI

if(adapter->vlgrp && (rx_desc->status & E1000_RXD_STAT_VP)) {

vlan_hwaccel_receive_skb(skb, adapter->vlgrp,

le16_to_cpu(rx_desc->special &

E1000_RXD_SPC_VLAN_MASK));

} else {

netif_receive_skb(skb);

}

#else /* CONFIG_E1000_NAPI */

/*

采用正常的中断方式处理数据包。实际上就是调用netif_rx将接收到的skb放到接收缓冲区列表当中,然后就返回了。接收缓冲区队列中的数据由随后执行的软中断处理函数进一步处理。

*/

if(adapter->vlgrp && (rx_desc->status & E1000_RXD_STAT_VP)) {

vlan_hwaccel_rx(skb, adapter->vlgrp,

le16_to_cpu(rx_desc->special &

E1000_RXD_SPC_VLAN_MASK));

} else {

netif_rx(skb);

}

#endif /* CONFIG_E1000_NAPI */

netdev->last_rx = jiffies;

rx_desc->status = 0;

buffer_info->skb = NULL;

if(++i == rx_ring->count) i = 0;

rx_desc = E1000_RX_DESC(*rx_ring, i);

}

rx_ring->next_to_clean = i;

e1000_alloc_rx_buffers(adapter);

return cleaned;

}

网卡的硬件中断处理函数主要的工作就是将DMA传输过来的数据包(skbuff)插入相应的接收缓冲区队列中,然后调用netif_rx函数将sk_buff插入接收缓冲区队列中就返回了。而在e1000的网卡中断处理程序当中用到了比较新的技术NAPI,这种技术是采用轮询的机制替换中断模式,加快网络数据的处理速度,详细的内容见《NAPI技术在Linux网络驱动上的应用》。这里我暂时只分析传统的中断机制。

四、网络处理的软中断机制分析

详见《网络处理的软中断机制分析》。

分享到:
评论

相关推荐

    linux项目工程资料-linux系统网络编程学习笔记.zip

    该项目以Linux内核为核心,围绕其构建了一个完整的操作系统,包括各种系统工具、库、应用程序和硬件支持。 以下是Linux项目的一些主要特点和资料介绍: 开放源代码:Linux项目的所有源代码都是公开的,并允许任何...

    linux内核笔记-(系统管理-内核分析-项目专题)

    启动 常用工具 系统安装配置 管理脚本语言 数据库 网络服务 安全 gcc socket编程 文件和设备编程 进程和线程编程 内核分析: 启动 中断 内存 进程 网络 系统调用 文件系统 驱动 经验 项目专题: LFYOS OSKit ...

    linux项目工程资料-Linux 系统学习笔记.zip

    该项目以Linux内核为核心,围绕其构建了一个完整的操作系统,包括各种系统工具、库、应用程序和硬件支持。 以下是Linux项目的一些主要特点和资料介绍: 开放源代码:Linux项目的所有源代码都是公开的,并允许任何...

    《Linux从入门到精通》

    Linux操作系统入门书,比较全面 目录 0 前言 0.1 什么是Linux? 0.2 本手册概述 0.3 速查信息 0.4 从以前版本的Red Hat Linux升级 0.5 开发者的话 0.6 编者的话 1 Red Hat Linux 5.1新特性 1.1 安装性能的...

    Linux从入门到精通

    E.9.4 为什么Linux只能看到我的内存的一部分? E.9.5 我的奔腾或更高档的机器有超过64M的内存, 但是它似乎很迟钝. 如果我用mem=64M让Linux只用64M内存, 速度似乎加快了. 这是怎么回事? 我该做什么? E.9.6 我有Red ...

    Linux内核移植笔记 | 06 – 移植Linux 3.4.2 内核到JZ2440(移植DM9000网卡驱动,支持网络,支持NFS挂载)

    进入内核源码目录里面,找到 arch/arm/mach-s3c24xx/目录并进入,找到如下这两个文件: mach-smdk2440.c mach-mini2440.c 下面将 mach-mini2440.c 中关于网卡的配置代码,移植到 mach-smdk2440.c 中。 添加头文件 ...

    linux从入门到精通.chm

    E.9.4 为什么Linux只能看到我的内存的一部分? E.9.5 我的奔腾或更高档的机器有超过64M的内存, 但是它似乎很迟钝. 如果我用mem=64M让Linux只用64M内存, 速度似乎加快了. 这是怎么回事? 我该做什么? E.9.6 我有Red ...

    RedHat Enterprise Linux 5 Update 2 x64-XiSO[bit torrent]

    RHEL 5 将是 RedHat 的商业服务器操作系统版本的第四次重要版本发布,RedHat 酝酿发布 RHEL 5 已经超过了两年, 主要变化包括 Linux 内核由 2.6.9 升级为 2.6.18,支持 Xen 虚拟化技术,集群存储等。 RHEL 5 的...

    2005详细介绍Linux从入门到精通

    E.9.4 为什么Linux只能看到我的内存的一部分? E.9.5 我的奔腾或更高档的机器有超过64M的内存, 但是它似乎很迟钝. 如果我用mem=64M让Linux只用64M内存, 速度似乎加快了. 这是怎么回事? 我该做什么? E.9.6 我有Red ...

    Redhat linux RHEL5最新功能

    Red Hat酝酿发布RHEL 5已经超过了两年,主要变化包括Linux内核由2.6.9升级为2.6.18, 支持Xen虚拟化技术, 集群存储等。RHEL5的版本主要分为Desktop和Sever两个版本。 Desktop版本分为: · Red Hat Enterprise Linux...

    Linux命令笔记

    第一部分:常用命令 常用命令/文件处理 0:基础功能 1:ls 显示文件目录 语法:ls 选项[-ald][文件或目录] 2:cat 显示文件内容(内容少适合) 语法:cat[文件名] 3:tac 显示文件内容 语法:tac[文件名] 4:more 分页...

    无线网络路由器密码破解工具绿色最新版

    另外既然该问题出自于Linux内核的无线路由器,那么对于采用Linux内核的其他无线路由器是否存在问题呢?由于实验环境有限,所以请感兴趣的读者自行尝试,例如Linksys的路由器,毕竟他们也是采用Linux内核的。

    无线网络路由器密码破解工具_绿色版

    另外既然该问题出自于Linux内核的无线路由器,那么对于采用Linux内核的其他无线路由器是否存在问题呢?由于实验环境有限,所以请感兴趣的读者自行尝试,例如Linksys的路由器,毕竟他们也是采用Linux内核的。 五、...

    无线网络路由器密码破解工具 最新修改

    另外既然该问题出自于Linux内核的无线路由器,那么对于采用Linux内核的其他无线路由器是否存在问题呢?由于实验环境有限,所以请感兴趣的读者自行尝试,例如Linksys的路由器,毕竟他们也是采用Linux内核的。 五、...

    [小e笔记]之10gR2+redhat5.3+32bit+RAC安装

    2.10安装Enterprise Linux软件程序包 39 2.11配置内核参数 39 2.12 修改/etc/hosts文件 40 2.13 配置hangcheck timer 内核模块 40 2.14 为Oracle ASM创建磁盘分区 41 2.15 安装oracleasmlib程序包 41 2.16 为...

    嵌入式学习笔记

    这个嵌入式学习笔记记录了本人多年来从事嵌入式linux+arm开发所遇到的重点难点,也包含了各大嵌入式...内容包含了进程、线程、网络编程、设备驱动、内核开发、根文件系统、U_BOOT、linux各种命令等等,精简却很给力。

    Google Android SDK开发范例大全(完整版)

    如今,很多基于网络或有网络支持的设备都运行某种 Linux 内核。这是一种可靠的平台:可经济有效地进行部署和提供支持,并且可直接作为面向部署的良好的设计方法。这些设备的 UI 通常是基于 HTML 的,可通过 PC 或 ...

    MobilePassThrough:使笔记本计算机上的GPU直通变得容易且易于使用!

    在主机系统(Linux)上: 自动安装所需的依赖项 自动配置内核参数以支持GPU直通 自动安装Bumblebee和Nvidia GPU驱动程序 自动检查您的设备是否与GPU直通兼容以及扩展到何种程度。 自动创建和配置为GPU直通完全...

    若干源程序资料12.rar

    2012-06-11 21:44 6,947,979 Linux内核完全注释V3.0书签版(带源码).rar 2012-06-11 21:31 11,599 MATLAB仿真程序OFDM程序.txt 2012-06-11 21:37 14,584,477 msdn for vb6.0简体中文版.zip 2012-06-11 21:02 12,288 ...

Global site tag (gtag.js) - Google Analytics