Skip to content

Instantly share code, notes, and snippets.

@luftreich
Last active August 29, 2015 14:05
Show Gist options
  • Select an option

  • Save luftreich/d3657f55105634ac6e8c to your computer and use it in GitHub Desktop.

Select an option

Save luftreich/d3657f55105634ac6e8c to your computer and use it in GitHub Desktop.
android4.0 suspend/resume的流程
http://blog.csdn.net/suntao222/article/details/8162938
android4.0 suspend/resume的流程
跟休眠唤醒相关的文件:
[html] view plaincopy
linux_source/kernel/power/main.c
linux_source/kernel/power/earlysuspend.c
linux_source/kernel/power/wakelock.c
linux_source/kernel/power/suspend.c
linux_source/kernel/power/power.h
linux_source/kernel/power/process.c
linux_source/drivers/base/power/main.c
linux_source/arch/arm/plat-samsung/pm.c
android\hardware\libhardware_legacy\power\power.c
android\frameworks\base\core\jni\android_os_Power.cpp
android\frameworks\base\core\java\android\os\Power.java
android\frameworks\base\core\java\android\os\PowerManager.java
android\frameworks\base\services\java\com\android\server\PowerManagerService.java
android\frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java
Android 休眠过程
当用户读写/sys/power/state时,linux_source/kernel/power/main.c中的state_store()函数会被调用。其中,android的early_suspend会执行request_suspend_state(state); 而标准的linux休眠则执行error = enter_state(state)
[cpp] view plaincopy
linux_source/kernel/power/main.c
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
#ifdef CONFIG_SUSPEND
#ifdef CONFIG_EARLYSUSPEND
suspend_state_t state = PM_SUSPEND_ON;
#else
suspend_state_t state = PM_SUSPEND_STANDBY;
#endif
const char * const *s;
#endif
char *p;
int len;
int error = -EINVAL;
p = memchr(buf, '\n', n);
len = p ? p - buf : n;
/* First, check if we are requested to hibernate */
if (len == 4 && !strncmp(buf, "disk", len)) {
error = hibernate();
goto Exit;
}
#ifdef CONFIG_SUSPEND
for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) {
if (*s && len == strlen(*s) && !strncmp(buf, *s, len))
break;
}
if (state < PM_SUSPEND_MAX && *s)
#ifdef <strong><span style="color:#3366ff;">CONFIG_EARLYSUSPEND</span></strong>
if (state == PM_SUSPEND_ON || valid_state(state)) {
error = 0;
<span style="color:#3366ff;"><strong>request_suspend_state(state);</strong></span>
}
#else
error = enter_state(state);
#endif
#endif
Exit:
return error ? error : n;
}
在request_suspend_state(state)函数中,会调用early_suspend_work的工作队列,从而进入early_suspend()函数中
[cpp] view plaincopy
linux_source/kernel/power/earlysuspend.c
static DECLARE_WORK(early_suspend_work, early_suspend);
void request_suspend_state(suspend_state_t new_state)
{
unsigned long irqflags;
int old_sleep;
spin_lock_irqsave(&state_lock, irqflags);
old_sleep = state & SUSPEND_REQUESTED;
if (debug_mask & DEBUG_USER_STATE) {
struct timespec ts;
struct rtc_time tm;
getnstimeofday(&ts);
rtc_time_to_tm(ts.tv_sec, &tm);
pr_info("request_suspend_state: %s (%d->%d) at %lld "
"(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n",
new_state != PM_SUSPEND_ON ? "sleep" : "wakeup",
requested_suspend_state, new_state,
ktime_to_ns(ktime_get()),
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
}
if (!old_sleep && new_state != PM_SUSPEND_ON) {
state |= SUSPEND_REQUESTED;
<span style="color:#3366ff;"><strong>queue_work(suspend_work_queue, &early_suspend_work);</strong></span>
} else if (old_sleep && new_state == PM_SUSPEND_ON) {
state &= ~SUSPEND_REQUESTED;
wake_lock(&main_wake_lock);
<span style="color:#3366ff;"><strong>queue_work(suspend_work_queue, &late_resume_work);</strong></span>
}
requested_suspend_state = new_state;
spin_unlock_irqrestore(&state_lock, irqflags);
}
在early_suspend()函数中,首先要判断当前请求的状态是否还是suspend,若不是,则直接退出了;若是,函数会调用已经注册的early_suspend的函数,然后同步文件系统,最后释放main_wake_lock。
[cpp] view plaincopy
linux_source/kernel/power/earlysuspend.c
static void early_suspend(struct work_struct *work)
{
struct early_suspend *pos;
unsigned long irqflags;
int abort = 0;
mutex_lock(&early_suspend_lock);
spin_lock_irqsave(&state_lock, irqflags);
if (state == SUSPEND_REQUESTED)
state |= SUSPENDED;
else
abort = 1;
spin_unlock_irqrestore(&state_lock, irqflags);
if (abort) {
if (debug_mask & DEBUG_SUSPEND)
pr_info("early_suspend: abort, state %d\n", state);
mutex_unlock(&early_suspend_lock);
goto abort;
}
if (debug_mask & DEBUG_SUSPEND)
pr_info("early_suspend: call handlers\n");
list_for_each_entry(pos, &early_suspend_handlers, link) {
if (pos->suspend != NULL) {
if (debug_mask & DEBUG_VERBOSE)
pr_info("early_suspend: calling %pf\n", pos->suspend);
pos->suspend(pos);
}
}
mutex_unlock(&early_suspend_lock);
if (debug_mask & DEBUG_SUSPEND)
pr_info("early_suspend: sync\n");
sys_sync();
abort:
spin_lock_irqsave(&state_lock, irqflags);
if (state == SUSPEND_REQUESTED_AND_SUSPENDED)
<span style="color:#3366ff;"><strong>wake_unlock(&main_wake_lock);</strong></span>
spin_unlock_irqrestore(&state_lock, irqflags);
}
在wake_unlock()中,删除链表中wake_lock节点,判断当前是否存在wake_lock,若wake_lock的数目为0,则调用工作队列suspend_work,进入suspend状态。
[cpp] view plaincopy
linux_source/kernel/power/wakelock.c
static DECLARE_WORK(suspend_work, suspend);
void wake_unlock(struct wake_lock *lock)
{
int type;
unsigned long irqflags;
spin_lock_irqsave(&list_lock, irqflags);
type = lock->flags & WAKE_LOCK_TYPE_MASK;
#ifdef CONFIG_WAKELOCK_STAT
wake_unlock_stat_locked(lock, 0);
#endif
if (debug_mask & DEBUG_WAKE_LOCK)
pr_info("wake_unlock: %s\n", lock->name);
lock->flags &= ~(WAKE_LOCK_ACTIVE | WAKE_LOCK_AUTO_EXPIRE);
list_del(&lock->link);
list_add(&lock->link, &inactive_locks);
if (type == WAKE_LOCK_SUSPEND) {
long has_lock = has_wake_lock_locked(type);
if (has_lock > 0) {
if (debug_mask & DEBUG_EXPIRE)
pr_info("wake_unlock: %s, start expire timer, "
"%ld\n", lock->name, has_lock);
mod_timer(&expire_timer, jiffies + has_lock);
} else {
if (del_timer(&expire_timer))
if (debug_mask & DEBUG_EXPIRE)
pr_info("wake_unlock: %s, stop expire "
"timer\n", lock->name);
if (has_lock == 0){
<span style="color:#3366ff;"><strong>queue_work(suspend_work_queue, &suspend_work);</strong></span>
}
}
if (lock == &main_wake_lock) {
if (debug_mask & DEBUG_SUSPEND)
print_active_locks(WAKE_LOCK_SUSPEND);
#ifdef CONFIG_WAKELOCK_STAT
update_sleep_wait_stats_locked(0);
#endif
}
}
spin_unlock_irqrestore(&list_lock, irqflags);
}
EXPORT_SYMBOL(wake_unlock);
在suspend()函数中,先判断当前是否有wake_lock,若有,则退出;然后同步文件系统,最后调用pm_suspend()函数。
[cpp] view plaincopy
linux_source/kernel/power/wakelock.c
static void suspend(struct work_struct *work)
{
int ret;
int entry_event_num;
struct timespec ts_entry, ts_exit;
if (has_wake_lock(WAKE_LOCK_SUSPEND)) {
if (debug_mask & DEBUG_SUSPEND)
pr_info("suspend: abort suspend\n");
return;
}
entry_event_num = current_event_num;
sys_sync();
if (debug_mask & DEBUG_SUSPEND)
pr_info("suspend: enter suspend\n");
getnstimeofday(&ts_entry);
<span style="color:#3366ff;"><strong>ret = pm_suspend(requested_suspend_state);</strong></span>
getnstimeofday(&ts_exit);
if (debug_mask & DEBUG_EXIT_SUSPEND) {
struct rtc_time tm;
rtc_time_to_tm(ts_exit.tv_sec, &tm);
pr_info("suspend: exit suspend, ret = %d "
"(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", ret,
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, ts_exit.tv_nsec);
}
if (ts_exit.tv_sec - ts_entry.tv_sec <= 1) {
++suspend_short_count;
if (suspend_short_count == SUSPEND_BACKOFF_THRESHOLD) {
suspend_backoff();
suspend_short_count = 0;
}
} else {
suspend_short_count = 0;
}
if (current_event_num == entry_event_num) {
if (debug_mask & DEBUG_SUSPEND)
pr_info("suspend: pm_suspend returned with no event\n");
wake_lock_timeout(&unknown_wakeup, HZ / 2);
}
}
static DECLARE_WORK(suspend_work, suspend); //stone tag
在pm_suspend()函数中,enter_state()函数被调用,从而进入标准linux休眠过程。
[cpp] view plaincopy
linux_source/kernel/power/suspend.c
/**
* pm_suspend - Externally visible function for suspending system.
* @state: Enumerated value of state to enter.
*
* Determine whether or not value is within range, get state
* structure, and enter (above).
*/
int pm_suspend(suspend_state_t state)
{
if (state > PM_SUSPEND_ON && state < PM_SUSPEND_MAX){
return <span style="color:#3366ff;"><strong>enter_state(state);</strong></span>
}
return -EINVAL;
}
EXPORT_SYMBOL(pm_suspend);
在enter_state()函数中,首先检查一些状态参数,再同步文件系统,然后调用suspend_prepare()来冻结进程,最后调用suspend_devices_and_enter()让外设进入休眠。
[cpp] view plaincopy
linux_source/kernel/power/suspend.c
/**
* enter_state - Do common work of entering low-power state.
* @state: pm_state structure for state we're entering.
*
* Make sure we're the only ones trying to enter a sleep state. Fail
* if someone has beat us to it, since we don't want anything weird to
* happen when we wake up.
* Then, do the setup for suspend, enter the state, and cleaup (after
* we've woken up).
*/
int enter_state(suspend_state_t state)
{
int error;
if (!valid_state(state))
return -ENODEV;
if (!mutex_trylock(&pm_mutex))
return -EBUSY;
printk(KERN_INFO "PM: Syncing filesystems ... ");
sys_sync();
printk("done.\n");
pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
<span style="color:#3366ff;"><strong>error = suspend_prepare();</strong> </span>
if (error)
goto Unlock;
if (suspend_test(TEST_FREEZER))
goto Finish;
pr_debug("PM: Entering %s sleep\n", pm_states[state]);
pm_restrict_gfp_mask();
<span style="color:#3366ff;"><strong>error = suspend_devices_and_enter(state);</strong></span>
pm_restore_gfp_mask();
Finish:
pr_debug("PM: Finishing wakeup.\n");
<span style="color:#3366ff;"><strong>suspend_finish();</strong></span>
Unlock:
mutex_unlock(&pm_mutex);
return error;
}
在suspend_prepare()函数中,先通过pm_prepare_console();给suspend分配一个虚拟终端来输出信息,再广播一个系统进入suspend的通报,关闭用户态的helper进程,然后调用suspend_freeze_processes()来冻结进程,最后会尝试释放一些内存。
[cpp] view plaincopy
linux_source/kernel/power/suspend.c
/**
* suspend_prepare - Do prep work before entering low-power state.
*
* This is common code that is called for each state that we're entering.
* Run suspend notifiers, allocate a console and stop all processes.
*/
static int suspend_prepare(void)
{
int error;
if (!suspend_ops || !suspend_ops->enter)
return -EPERM;
pm_prepare_console();
error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
if (error)
goto Finish;
error = usermodehelper_disable();
if (error)
goto Finish;
<span style="color:#3366ff;"><strong>error = suspend_freeze_processes();</strong></span>
if (!error)
return 0;
suspend_thaw_processes();
usermodehelper_enable();
Finish:
pm_notifier_call_chain(PM_POST_SUSPEND);
pm_restore_console();
return error;
}
在suspend_freeze_processes()函数中调用了freeze_processes()函数,而freeze_processes()函数中又调用了try_to_freeze_tasks()来完成冻结任务。在冻结过程中,会判断当前进程是否有wake_lock,若有,则冻结失败,函数会放弃冻结。
[cpp] view plaincopy
linux_source/kernel/power/power.h
static inline int suspend_freeze_processes(void)
{
return freeze_processes(); //stone tag
}
[cpp] view plaincopy
linux_source/kernel/power/process.c
/**
* freeze_processes - tell processes to enter the refrigerator
*/
int freeze_processes(void)
{
int error;
printk("Freezing user space processes ... ");
<span style="color:#3366ff;"><strong>error = try_to_freeze_tasks(true);</strong></span>
if (error)
goto Exit;
printk("done.\n");
printk("Freezing remaining freezable tasks ... ");
<span style="color:#3366ff;"><strong>error = try_to_freeze_tasks(false);</strong></span>
if (error)
goto Exit;
printk("done.");
oom_killer_disable();
Exit:
BUG_ON(in_atomic());
printk("\n");
return error;
}
到现在,所有的进程(也包括workqueue/kthread) 都已经停止了,内核态进程有可能在停止的时候握有一些信号量,所以如果这时候在外设里面去解锁这个信号量有可能会发生死锁, 所以在外设suspend()函数里面作lock/unlock锁要非常小心,建议不要在外设的suspend()里面等待锁。而且suspend的过程中,有一些log是无法输出的,所以一旦出现问题,非常难调试。
回到enter_state()函数中,在冻结进程完成后,调用suspend_devices_and_enter()函数让外设进入休眠。该函数中,首先休眠串口(之后不能再显示log,解决方法为在kernel配置选项的cmd_line中,添加”no_console_suspend”选项,或者直接注释掉suspend_console()函数),再通过dpm_suspend_start(PMSG_SUSPEND);函数调用各驱动的suspend函数。
[cpp] view plaincopy
linux_source/kernel/power/suspend.c
/**
* suspend_devices_and_enter - suspend devices and enter the desired system
* sleep state.
* @state: state to enter
*/
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
if (!suspend_ops)
return -ENOSYS;
trace_machine_suspend(state);
if (suspend_ops->begin) {
error = suspend_ops->begin(state);
if (error)
goto Close;
}
<span style="color:#ff6666;"><strong>//suspend_console();
</strong></span> suspend_test_start();
<span style="color:#3366ff;"><strong>error = dpm_suspend_start(PMSG_SUSPEND);</strong></span>
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend\n");
goto Recover_platform;
}
suspend_test_finish("suspend devices");
if (suspend_test(TEST_DEVICES))
goto Recover_platform;
<span style="color:#3366ff;"><strong>error = suspend_enter(state);</strong></span>
Resume_devices:
suspend_test_start();
<span style="color:#3366ff;"><strong>dpm_resume_end(PMSG_RESUME);</strong></span>
suspend_test_finish("resume devices");
<span style="color:#ff6666;"><strong>//resume_console();</strong></span>
Close:
if (suspend_ops->end)
suspend_ops->end();
trace_machine_suspend(PWR_EVENT_EXIT);
return error;
Recover_platform:
if (suspend_ops->recover)
suspend_ops->recover();
goto Resume_devices;
}
当外设进入休眠后,进入suspend_enter(),在suspend_enter()中suspend_ops->prepare()被调用,suspend_ops是板级的PM操作,依赖于具体的平台,在我samsung4412系统中,其注册在linux_source/arch/arm/plat-samsung/pm.c中,定义了suspend_ops->enter()函数。
[cpp] view plaincopy
linux_source/arch/arm/plat-samsung/pm.c
static const struct platform_suspend_ops s3c_pm_ops = {
.enter = s3c_pm_enter,
.prepare = s3c_pm_prepare,
.finish = s3c_pm_finish,
.valid = suspend_valid_only_mem,
};
接下来,首先关闭IRQ,关闭不用的CPU,休眠所有的系统设备和总线。最后调用 suspend_pos->enter() 来使CPU进入省电状态。这时候,整个休眠过程完成,代码的执行也就停在这里了。
[cpp] view plaincopy
linux_source/kernel/power/suspend.c
/**
* suspend_enter - enter the desired system sleep state.
* @state: state to enter
*
* This function should be called after devices have been suspended.
*/
static int suspend_enter(suspend_state_t state)
{
int error;
if (suspend_ops->prepare) {
error = suspend_ops->prepare();
if (error)
goto Platform_finish;
}
<span style="color:#3366ff;"><strong>error = dpm_suspend_noirq(PMSG_SUSPEND);</strong></span>
if (error) {
printk(KERN_ERR "PM: Some devices failed to power down\n");
goto Platform_finish;
}
if (suspend_ops->prepare_late) {
error = suspend_ops->prepare_late();
if (error)
goto Platform_wake;
}
if (suspend_test(TEST_PLATFORM))
goto Platform_wake;
<span style="color:#3366ff;"><strong>error = disable_nonboot_cpus();</strong>
</span> if (error || suspend_test(TEST_CPUS))
goto Enable_cpus;
arch_suspend_disable_irqs();
BUG_ON(!irqs_disabled());
error = syscore_suspend();
if (!error) {
if (!(suspend_test(TEST_CORE) || pm_wakeup_pending())) {
<strong><span style="color:#3366ff;">error = suspend_ops->enter(state);</span></strong>
events_check_enabled = false;
}
<span style="color:#3366ff;"><strong>syscore_resume();</strong></span>
}
arch_suspend_enable_irqs();
BUG_ON(irqs_disabled());
Enable_cpus:
enable_nonboot_cpus();
Platform_wake:
if (suspend_ops->wake)
suspend_ops->wake();
<span style="color:#3366ff;"><strong>dpm_resume_noirq(PMSG_RESUME);</strong>
</span>
Platform_finish:
if (suspend_ops->finish)
<span style="color:#3366ff;"><strong>suspend_ops->finish();</strong></span>
return error;
}
在suspend_pos->enter()所对应的函数中,代码最终停止在s3c_pm_arch_stop_clocks();处。
[cpp] view plaincopy
linux_source/arch/arm/plat-samsung/pm.c
/* s3c_pm_enter
*
* central control for sleep/resume process
*/
static int s3c_pm_enter(suspend_state_t state)
{
.....
/* save all necessary core registers not covered by the drivers */
s3c_pm_save_gpios();
s3c_pm_saved_gpios();
s3c_pm_save_uarts();
s3c_pm_save_core();
.....
/* send the cpu to sleep... */
<span style="color:#3366ff;"><strong>s3c_pm_arch_stop_clocks();</strong></span>
/* s3c_cpu_save will also act as our return point from when
* we resume as it saves its own register state and restores it
* during the resume. */
s3c_cpu_save(0, PLAT_PHYS_OFFSET - PAGE_OFFSET);
/* restore the cpu state using the kernel's cpu init code. */
<span style="color:#3366ff;"><strong>cpu_init();</strong></span>
s3c_pm_restore_core();
s3c_pm_restore_uarts();
s3c_pm_restore_gpios();
s3c_pm_restored_gpios();
.....
return 0;
}
Android 唤醒过程如下:
如果在休眠中系统被中断或者其他事件唤醒,接下来的代码就从suspend完成的地方开始执行,即pm.c中的s3c_pm_enter()中的cpu_init(),然后执行suspend_enter()的syscore_resume()函数,唤醒系统设备和总线,使能系统中断。然后回到suspend_devices_and_enter()函数中,dpm_resume_end(PMSG_RESUME)发出resume消息唤醒每个设备,使能终端。当suspend_devices_and_enter()执行完成后,系统外设已经唤醒,但进程依然是冻结的状态,返回到enter_state函数中,调用suspend_finish()函数。在suspend_finish()函数中,解冻进程和任务,使能用户空间helper进程,广播一个系统从suspend状态退出的notify,唤醒终端。
[cpp] view plaincopy
linux_source/kernel/power/suspend.c
/**
* suspend_finish - Do final work before exiting suspend sequence.
*
* Call platform code to clean up, restart processes, and free the
* console that we've allocated. This is not called for suspend-to-disk.
*/
static void suspend_finish(void)
{
suspend_thaw_processes();
usermodehelper_enable();
<span style="color:#3366ff;"><strong>pm_notifier_call_chain(PM_POST_SUSPEND); //重要,唤醒系统和RIL等进程</strong>
</span> pm_restore_console();
}
当所有的唤醒已经结束以后,用户进程都已经开始运行了,但没点亮屏幕,唤醒通常会是以下的几种原因:
如果是来电,那么Modem会通过中断脚唤醒AP,rild是一个守护进程,在醒来后不断从modem usb数据端口polling数据,当发现是incoming call或者incoming sms事件后就通知WindowManager有来电响应,这样就会远程调用PowerManagerService来写”on”到 /sys/power/state 来调用late resume(),执行点亮屏幕等操作。
用户按键事件会送到WindowManager中,WindowManager会处理这些按键事件,按键分为几种情况,如果按键不是唤醒键,那么WindowManager会主动放弃wakeLock来使系统进入再次休眠;如果按键是唤醒键,那么WindowManger就会调用PowerManagerService中的接口来执行late Resume。 当”on”被写入到/sys/power/state之后,同early_suspend过程,request_suspend_state()被调用,只是执行的工作队列变为late_resume_work。在late_resume函数中,唤醒调用了early_suspend的设备。
@luftreich

Copy link
Copy Markdown
Author

@luftreich

Copy link
Copy Markdown
Author

PHY指物理层,OSI的最底层。
一般指与外部信号接口的芯片。
以太网PHY芯片
网络中最基础的部件是什么?不是交换机也不是路由器,而是小小的不起眼但又无处不在的网卡。如果在5年前,或许网卡与您无关,但在如今这网络的时代,无论是上网冲浪还是联网玩游戏,都离不开网卡,更何况,就算您不食人间烟火,多数主板上也会为您集成一块板载网卡。所以,对于想迈入网络之门的读者而言,先认识网卡,会让您在进行各种网络应用时更得心应手。
一、网卡的主要特点
网卡(Network Interface Card,简称NIC),也称网络适配器,是电脑与局域网相互连接的设备。无论是普通电脑还是高端服务器,只要连接到局域网,就都需要安装一块网卡。如果有必要,一台电脑也可以同时安装两块或多块网卡。
电脑之间在进行相互通讯时,数据不是以流而是以帧的方式进行传输的。我们可以把帧看做是一种数据包,在数据包中不仅包含有数据信息,而且还包含有数据的发送地、接收地信息和数据的校验信息。一块网卡包括OSI模型的两个层——物理层和数据链路层。物理层定义了数据传送与接收所需要的电与光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层设备提供标准接口。数据链路层则提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能。
网卡的功能主要有两个:一是将电脑的数据封装为帧,并通过网线(对无线网络来说就是电磁波)将数据发送到网络上去;二是接收网络上其它设备传过来的帧,并将帧重新组合成数据,发送到所在的电脑中。网卡能接收所有在网络上传输的信号,但正常情况下只接受发送到该电脑的帧和广播帧,将其余的帧丢弃。然后,传送到系统CPU做进一步处理。当电脑发送数据时,网卡等待合适的时间将分组插入到数据流中。接收系统通知电脑消息是否完整地到达,如果出现问题,将要求对方重新发送。
需要说明的是网卡芯片也有“软硬”之分,特别是对与主板板载(LOM)的网卡芯片来说更是如此,这是怎么回事呢?大家知道,以太网接口可分为协议层和物理层。
  协议层是由一个叫MAC(Media Access Layer,媒体访问层)控制器的单一模块实现。
  物理层由两部分组成,即PHY(Physical Layer,物理层)和传输器。
  常见的网卡芯片都是把MAC和PHY集成在一个芯片中,但目前很多主板的南桥芯片已包含了以太网MAC控制功能,只是未提供物理层接口,因此,需外接PHY芯片以提供以太网的接入通道。这类PHY网络芯片就是俗称的“软网卡芯片”,常见的PHY功能的芯片有RTL8201BL、VT6103等等。
“软网卡”一般将网络控制芯片的运算部分交由处理器或南桥芯片处理,以简化线路设计,从而降低成本,但其多少会更多占用系统资源

二、图解网卡
以最常见的PCI接口的网卡为例,一块网卡主要由PCB线路板、主芯片、数据汞、金手指(总线插槽接口)、BOOTROM、EEPROM、晶振、RJ45接口、指示灯、固定片等等,以及一些二极管、电阻电容等组成。下面我们就来分别了解一下其中主要部件。
●主芯片

  网卡的主控制芯片是网卡的核心元件,一块网卡性能的好坏和功能的强弱多寡,主要就是看这块芯片的质量。以常见的Realtek公司推出的RTL8139C和RTL8139D为例,二者首先在封装上略有不同,前者是128pin QFP/LQFP而后者为100pin,其次在搭配的EEPROM上,8139C比后者多出了对93c56的支持,而8139D是93C46。但是在功能方面,8139D更强一些,它多提供了对PCI Multi-function和PCI-bridge I/F的支持,PCI Multi-function允许把RTL8139D芯片和其他的功能芯片(如硬件调制解调芯片)设计在同块PCB板上协同工作来做成不同种类的多功能卡,在其中8139起的作用是辨别LAN信号还是PCI总线信号的作用;8139D还增强了电源管理功能。 

如果按网卡主芯片的速度来划分,常见的10/100M自适应网卡芯片有Realtek 8139系列/810X系列、VIA VT610_系列、Intel 82550PM/82559系列、Broadcom 44xx系列、3COM 3C920系列、Davicom DM9102、Mxic MX98715等等。
常见的10/100/1000M自适应网卡芯片有Intel的8254_系列,Broadcom的BCM57*_系列,Marvell的88E8001/88E8053/88E806_系列,Realtek的RTL8169S-32/64、RTL8110S-32/64(LOM)、RTL8169SB、RTL8110SB(LOM)、RTL8168(PCI Express)、RTL8111(LOM、PCI Express)系列,VIA的VT612*系列等等。
Marvell的88E8001千兆芯片
需要说明的是网卡芯片也有“软硬”之分,特别是对与主板板载(LOM)的网卡芯片来说更是如此,这是怎么回事呢?大家知道,以太网接口可分为协议层和物理层。
协议层是由一个叫MAC(Media Access Layer,媒体访问层)控制器的单一模块实现。
物理层由两部分组成,即PHY(Physical Layer,物理层)和传输器。
常见的网卡芯片都是把MAC和PHY集成在一个芯片中,但目前很多主板的南桥芯片已包含了以太网MAC控制功能,只是未提供物理层接口,因此,需外接PHY芯片以提供以太网的接入通道。这类PHY网络芯片就是俗称的“软网卡芯片”,常见的PHY功能的芯片有RTL8201BL、VT6103等等。

   “软网卡”一般将网络控制芯片的运算部分交由处理器或南桥芯片处理,以简化线路设计,从而降低成本,但其多少会更多占用系统资源. 

●BOOTROM
BOOTROM插座也就是常说的无盘启动ROM接口,其是用来通过远程启动服务构造无盘工作站的。远程启动服务(Remoteboot,通常也叫RPL)使通过使用服务器硬盘上的软件来代替工作站硬盘引导一台网络上的工作站成为可能。网卡上必须装有一个RPL(Remote Program Load远程初始程序加载)ROM芯片才能实现无盘启动,每一种RPL ROM芯片都是为一类特定的网络接口卡而制作的,它们之间不能互换。带有RPL的网络接口卡发出引导记录请求的广播(broadcasts),服务器自动的建立一个连接来响应它,并加载MS-DOS启动文件到工作站的内存中。
此外,在BOOTROM插槽中心一般还有一颗93C46、93LC46或93c56的EEPROM芯片(93C56是128_16bit的EEPROM,而93C46是64_16bit的EEPROM),它相当于网卡的BIOS,里面记录了网卡芯片的供应商ID、子系统供应商ID、网卡的MAC地址、网卡的一些配置,如总线上PHY的地址,BOOTROM的容量,是否启用BOOTROM引导系统等内容。主板板载网卡的EEPROM信息一般集成在主板BIOS中。
●LED指示灯
一般来讲,每块网卡都具有1个以上的LED(Light Emitting Diode发光二极管)指示灯,用来表示网卡的不同工作状态,以方便我们查看网卡是否工作正常。典型的LED指示灯有Link/Act、Full、Power等。Link/Act表示连接活动状态,Full表示是否全双工(Full Duplex),而Power是电源指示(主要用在USB或PCMCIA网卡上)等。
●网络唤醒接口
早期网卡上还有一个专门的3芯插座网络唤醒(WOL)接口(PCI2.1标准网卡),Wake On LAN(网络唤醒)提供了远程唤醒计算机的功能,它是IBM公司和Intel公司于1996年10月成立的先进管理性联盟(Advanced Manageability Alliance)的一项成果,它可以让管理员在非工作时间远程唤醒计算机,并使它们自动完成一些管理服务,例如软件的更新或者病毒扫描。它也是Wired for Management基本规范中的一部分。网络唤醒的工作原理是先由一个管理软件包发出一个基于Magic Packet标准的唤醒帧,支持网络唤醒的网卡收到唤醒帧后对其进行分析并确定该帧是否包含本网卡的MAC地址。如果包含本网卡的MAC地址,该计算机系统就会自动进入开机状态。
目前主流的独立网卡或主板板载网卡都符合PCI2.2及以上的规范,所以不再需要这个接口,要启动网络唤醒功能,只需到主板BIOS中启用“Wake on PCI Card”功能即可。
●数据汞
数据汞是消费级PCI网卡上都具备的设备,数据汞也被叫做网络变压器或可称为网络隔离变压器。它在一块网卡上所起的作用主要有两个,一是传输数据,它把PHY送出来的差分信号用差模耦合的线圈耦合滤波以增强信号,并且通过电磁场的转换耦合到不同电平的连接网线的另外一端;一是隔离网线连接的不同网络设备间的不同电平,以防止不同电压通过网线传输损坏设备。除此而外,数据汞还能对设备起到一定的防雷保护作用。
●晶振
晶振是石英振荡器的简称,英文名为Crystal,它是时钟电路中最重要的部件,它的作用是向显卡、网卡、主板等配件的各部分提供基准频率,它就像个标尺,工作频率不稳定会造成相关设备工作频率不稳定,自然容易出现问题。由于制造工艺不断提高,现在晶振的频率偏差、温度稳定性、老化率、密封性等重要技术指标都很好,已不容易出现故障,但在选用时仍可留意一下晶振的质量。
例如某网卡的时钟电路采用了高精度的SKO25MHz的晶振,较可_保证了数据传输的精确同步性,大大减少了丢包的可能性,并且在线路的设计上尽量_近主芯片,使信号走线的长度大大缩短,可_性进一步增加。而如果采用劣质晶振,这样做虽然可以降低一点网卡成本,但因为频率的准确性问题,极易造成传输过程中的数据丢包的情况。
●网线接口
在桌面消费级网卡中常见网卡接口有BNC接口和RJ-45接口(类似电话的接口),也有两种接口均有的双口网卡。接口的选择与网络布线形式有关,在小型共享式局域网中,BNC口网卡通过同轴电缆直接与其它计算机和服务器相连;RJ-45口网卡通过双绞线连接集线器(HUB)或交换机,再通过集线器或交换机连接其它计算机和服务器。
目前BNC接口这种接口类型的网卡已很少见,主要因为用细同轴电缆作为传输介质的网络就比较少及组网方式问题较多有关。RJ-45是8芯线,而电话线的接口是4芯的,通常只接2芯线(ISDN的电话线接4芯线);但大家可以仔细看看,其实10M网卡的RJ-45插口也只用了1、2、3、6四根针,而100M或1000M网卡的则是八根针都是全的,这也是区别10M和100M网卡的一种方法(见上图8)。
●传输介质类型
说到网卡,就顺便就谈谈与网卡连接的双绞线。
双绞线,是由许多在一个绝缘外套中的对线组成的数据传输线,它的特点就是价格便宜,现在的网卡大部分都是使用的双绞线做为传输线缆。双绞线一般用于星型网的布线连接,两端安装有RJ-45头(水晶头),连接网卡与集线器,最大网线长度为100米左右。
双绞线有STP(屏蔽双绞线)和UTP(非屏蔽双绞线)两种。STP的双绞线内有一层金属隔离膜,在数据传输时可减少电磁干扰,所以它的稳定性较高。而UTP内没有这层金属膜,所以它的稳定性较差,但它的优势就是价格便宜。其中STP(屏蔽双绞线)主要分为3类和5类两种线,UTP(非屏蔽双绞线)主要分为3类/4类/5类/超5类/6类几种,一般网络主要使用的是5类双绞线,5类双绞线外层保护胶皮厚,胶皮上标注“CAT5”字样。超5类双绞线属非屏蔽双绞线,与普通5类双绞线比较,超5类双绞线在传送信号时衰减更小,抗干扰能力更强,在100M网络中,用户设备的受干扰程度只有普通5类线的1/4,其也是目前应用的主流。
●总线接口
网卡要与电脑相连接才能正常使用,电脑上各种接口层出不穷,这也造成了网卡所采用的总线接口类型纷呈。此外,提到总线接口,需要说明的是人们一般将这类接口俗称为“金手指”,为什么叫金手指呢?是因为这类插卡的线脚采用的是镀钛金(或其它金属),保证了反复插拔时的可_接触,既增大了自身的抗干扰能力又减少了对其他设备的干扰。
为了方便您了解,下面我们就分别来图解一下常见的各种接口类型的网卡。
①ISA接口网卡
ISA是早期网卡使用的一种总线接口,ISA网卡采用程序请求I/O方式与CPU进行通信,这种方式的网络传输速率低,CPU资源占用大,其多为10M网卡,目前在市面上基本上看不到有ISA总线类型的网卡,笔者从旧件堆中找到了几款ISA网卡,D-LINK的产品,居然用橡皮擦清洁金手指上机后还能用。
②PCI接口网卡
PCI(peripheral component interconnect)总线插槽仍是目前主板上最基本的接口。其基于32位数据总线,可扩展为64位,它的工作频率为33MHz/66MHz。数据传输率为每秒132MB(32*33MHz/8)。目前PCI接口网卡仍是家用消费级市场上的绝对主流。
③PCI-X接口网卡
PCI-X是PCI总线的一种扩展架构,它与PCI总线不同的是,PCI总线必须频繁的于目标设备和总线之间交换数据,而PCI-X则允许目标设备仅于单个PCI-X设备看已进行交换,同时,如果PCI-X设备没有任何数据传送,总线会自动将PCI-X设备移除,以减少PCI设备间的等待周期。所以,在相同的频率下,PCI-X将能提供比PCI高14-35%的性能。目前服务器网卡经常采用此类接口的网卡。
④PCI-E接口网卡
PCI Express 1X接口已成为目前主流主板的必备接口。不同与并行传输,PCI Express接口采用点对点的串行连接方式,PCI Express接口根据总线接口对位宽的要求不同而有所差异,分为PCI Express 1X(标准250MB/s,双向500MB/s)、2X(标准500MB/s)、4X(1GB/s)、8X(2GB/s)、16X(4GB/s)、32X(8GB/s)。采用PCI-E接口的网卡多为千兆网卡。
⑤USB接口网卡
在目前的电脑上很难找到没有USB接口(Universal Serial Bus,通用串行总线)的,USB总线分为USB2.0和USB1.1标准。USB1.1标准的传输速率的理论值是12Mbps,而USB2.0标准的传输速率可以高达480Mbps,目前的USB有线网卡多为USB2.0标准的。
⑥PCMCIA接口网卡
PCMCIA接口是笔记本电脑专用接口,PCMCIA总线分为两类,一类为16位的PCMCIA,另一类为32位的CardBus,CardBus网卡的最大吞吐量接近90Mbps,其是目前市售笔记本网卡的主流。
⑦Mini-PCI接口网卡
MiniPCI接口是在台式机PCI接口基础上扩展出的适用于笔记本电脑的接口标准,其速度和PCI标准相当,很多此类产品都是无线网卡。
除此而外,市场上还有AMR等接口的网卡等等,限于篇幅及其流行度,本文就不一一介绍了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment