关键词:AT24、I2C、nvmem、EEPROM。

1. AT24C介绍

AT24C是一款采用I2C通信的EEPROM,相关驱动涉及到I2C和nvmem。

I2C是读写数据的通道,nvmem将AT24C注册为nvmem设备。

2.源码分析

2.1 DTS

at24是挂在i2c总线下的设备,硬件接到哪个i2c,DTS中也需要对应修改。

其中需要注意的是,status不能是disabled,pinctrl需要配置。

其中at24的campatible需要和代码对应。

        i2c3 {
compatible = "snps,designware-i2c";
//status = "disabled";--------------------------注释掉disabled才能使用此i2c。
reg = <0xfc407000 0x1000>;
interrupts = <>;
clocks = <&high_apb_clk>;
pinctrl-names = "default";
pinctrl- = <&i2c3_2>;
#address-cells = <>;
#size-cells = <>;
       clock-frequency = <400000>;----------------------调整对应i2c的频率,其中400K和1M需要设置时序才能正确使用。
            at24@ {
compatible = "at24,24cm02";
reg = <0x50>;
};
};

2.2 AT24初始化

at24的probe主要做数据有效检查、根据i2c的capability决定读写函数、读1字节验证功能、最后注册对应的nvmem设备。

static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct at24_platform_data chip;
kernel_ulong_t magic = ;
bool writable;
int use_smbus = ;
int use_smbus_write = ;
struct at24_data *at24;
int err;
unsigned i, num_addresses;
u8 test_byte; ...
if (!is_power_of_2(chip.byte_len))-----------------------------------------数据检查
dev_warn(&client->dev,
"byte_len looks suspicious (no power of 2)!\n");
if (!chip.page_size) {
dev_err(&client->dev, "page_size must not be 0!\n");
return -EINVAL;
}
if (!is_power_of_2(chip.page_size))
dev_warn(&client->dev,
"page_size looks suspicious (no power of 2)!\n"); /* Use I2C operations unless we're stuck with SMBus extensions. */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {-------------是否具备I2C_FUNC_I2C,如果不具备则判断是否具备下面能力。然后决定是否使用SMBus。
...
} if (chip.flags & AT24_FLAG_TAKE8ADDR)
num_addresses = ;
else
num_addresses = DIV_ROUND_UP(chip.byte_len,
(chip.flags & AT24_FLAG_ADDR16) ? : ); at24 = devm_kzalloc(&client->dev, sizeof(struct at24_data) +
num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);
if (!at24)
return -ENOMEM; mutex_init(&at24->lock);
at24->use_smbus = use_smbus;
at24->use_smbus_write = use_smbus_write;
at24->chip = chip;
at24->num_addresses = num_addresses; if ((chip.flags & AT24_FLAG_SERIAL) && (chip.flags & AT24_FLAG_MAC)) {
dev_err(&client->dev,
"invalid device data - cannot have both AT24_FLAG_SERIAL & AT24_FLAG_MAC.");
return -EINVAL;
} if (chip.flags & AT24_FLAG_SERIAL) {--------------------------------------根据flags,以及是否使用SMBus,选择合适的read_func()/write_func()。
at24->read_func = at24_eeprom_read_serial;
} else if (chip.flags & AT24_FLAG_MAC) {
at24->read_func = at24_eeprom_read_mac;
} else {
at24->read_func = at24->use_smbus ? at24_eeprom_read_smbus
: at24_eeprom_read_i2c;
} if (at24->use_smbus) {
if (at24->use_smbus_write == I2C_SMBUS_I2C_BLOCK_DATA)
at24->write_func = at24_eeprom_write_smbus_block;
else
at24->write_func = at24_eeprom_write_smbus_byte;
} else {
at24->write_func = at24_eeprom_write_i2c;
} writable = !(chip.flags & AT24_FLAG_READONLY);
if (writable) {---------------------------------------------------------------------------------write_max决定后面i2c写大小,大于此数字会被拆分。
if (!use_smbus || use_smbus_write) { unsigned write_max = chip.page_size; if (write_max > io_limit)
write_max = io_limit;
if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)
write_max = I2C_SMBUS_BLOCK_MAX;
at24->write_max = write_max; /* buffer (data + address at the beginning) */
at24->writebuf = devm_kzalloc(&client->dev,
write_max + , GFP_KERNEL);
if (!at24->writebuf)
return -ENOMEM;
} else {
dev_warn(&client->dev,
"cannot write due to controller restrictions.");
}
} at24->client[] = client; /* use dummy devices for multiple-address chips */
for (i = ; i < num_addresses; i++) {
at24->client[i] = i2c_new_dummy(client->adapter,
client->addr + i);
if (!at24->client[i]) {
dev_err(&client->dev, "address 0x%02x unavailable\n",
client->addr + i);
err = -EADDRINUSE;
goto err_clients;
}
} i2c_set_clientdata(client, at24); /*
* Perform a one-byte test read to verify that the
* chip is functional.
*/
err = at24_read(at24, , &test_byte, );--------------------------------读一字节进行测试。
if (err) {
err = -ENODEV;
goto err_clients;
} at24->nvmem_config.name = dev_name(&client->dev);-----------------------注册nvmem设备,对应设备/sys/bus/i2c/devices/x-0050/eeprom。
at24->nvmem_config.dev = &client->dev;
at24->nvmem_config.read_only = !writable;
at24->nvmem_config.root_only = true;
at24->nvmem_config.owner = THIS_MODULE;
at24->nvmem_config.compat = true;
at24->nvmem_config.base_dev = &client->dev;
at24->nvmem_config.reg_read = at24_read;--------------------------------对于非SMBus设备,对应at24_eeprom_read_i2c()。
at24->nvmem_config.reg_write = at24_write;------------------------------对非SMBus设备,对应at24_eeprom_write_i2c()。
at24->nvmem_config.priv = at24;
at24->nvmem_config.stride = ;
at24->nvmem_config.word_size = ;
at24->nvmem_config.size = chip.byte_len; at24->nvmem = nvmem_register(&at24->nvmem_config); if (IS_ERR(at24->nvmem)) {
err = PTR_ERR(at24->nvmem);
goto err_clients;
}
...
/* export data to kernel code */
if (chip.setup)
chip.setup(at24->nvmem, chip.context); return ; err_clients:
for (i = ; i < num_addresses; i++)
if (at24->client[i])
i2c_unregister_device(at24->client[i]); return err;
} static int at24_remove(struct i2c_client *client)
{
struct at24_data *at24;
int i; at24 = i2c_get_clientdata(client); nvmem_unregister(at24->nvmem); for (i = ; i < at24->num_addresses; i++)
i2c_unregister_device(at24->client[i]); return ;
} static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.acpi_match_table = ACPI_PTR(at24_acpi_ids),
},
.probe = at24_probe,
.remove = at24_remove,
.id_table = at24_ids,
};

2.3 I2C读写

上层应用的读写,在底层是有限制的。

write就变成了一字节一字节写,read一次只能读取一个128字节大小。

static int at24_read(void *priv, unsigned int off, void *val, size_t count)
{
struct at24_data *at24 = priv;
char *buf = val; if (unlikely(!count))
return count; mutex_lock(&at24->lock); while (count) {---------------------------------------------------上层传入的count大小,交给i2c进行去读。但是每次读取多少字节受限于i2c,一次循环。
int status; status = at24->read_func(at24, buf, off, count);--------------每次i2c读取操作,buf、off递增,count递减。
if (status < ) {
mutex_unlock(&at24->lock);
return status;
}
buf += status;
off += status;
count -= status;
} mutex_unlock(&at24->lock); return ;
} static int at24_write(void *priv, unsigned int off, void *val, size_t count)
{
struct at24_data *at24 = priv;
char *buf = val; if (unlikely(!count))
return -EINVAL; /*
* Write data to chip, protecting against concurrent updates
* from this host, but not from other I2C masters.
*/
mutex_lock(&at24->lock); while (count) {---------------------------------------------------上层传下来的count,不一定一次写完。此处进行loop。
int status; status = at24->write_func(at24, buf, off, count);--------------每次只写1字节,所以效率很低。
if (status < ) {
mutex_unlock(&at24->lock);
return status;
}
buf += status;
off += status;
count -= status;
} mutex_unlock(&at24->lock); return ;
} static ssize_t at24_eeprom_read_i2c(struct at24_data *at24, char *buf,
unsigned int offset, size_t count)
{
unsigned long timeout, read_time;
struct i2c_client *client;
struct i2c_msg msg[];
int status, i;
u8 msgbuf[]; memset(msg, , sizeof(msg));
client = at24_translate_offset(at24, &offset); if (count > io_limit)
count = io_limit;------------------------------------------------不管上层传入读取多大数据,都受限于io_limit,这里为一个128字节。 /*
* When we have a better choice than SMBus calls, use a combined I2C
* message. Write address; then read up to io_limit data bytes. Note
* that read page rollover helps us here (unlike writes). msgbuf is
* u8 and will cast to our needs.
*/
i = ;
if (at24->chip.flags & AT24_FLAG_ADDR16)
msgbuf[i++] = offset >> ;
msgbuf[i++] = offset; msg[].addr = client->addr;
msg[].buf = msgbuf;
msg[].len = i; msg[].addr = client->addr;
msg[].flags = I2C_M_RD;
msg[].buf = buf;
msg[].len = count; loop_until_timeout(timeout, read_time) {
status = i2c_transfer(client->adapter, msg, );
if (status == )
status = count; dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
count, offset, status, jiffies); if (status == count)
return count;
} return -ETIMEDOUT;
} static ssize_t at24_eeprom_write_i2c(struct at24_data *at24, const char *buf,
unsigned int offset, size_t count)
{
unsigned long timeout, write_time;
struct i2c_client *client;
struct i2c_msg msg;
ssize_t status = ;
int i = ; client = at24_translate_offset(at24, &offset);
count = at24_adjust_write_count(at24, offset, count); msg.addr = client->addr;
msg.flags = ; /* msg.buf is u8 and casts will mask the values */
msg.buf = at24->writebuf;
if (at24->chip.flags & AT24_FLAG_ADDR16)
msg.buf[i++] = offset >> ; msg.buf[i++] = offset;
memcpy(&msg.buf[i], buf, count);
msg.len = i + count; loop_until_timeout(timeout, write_time) {
status = i2c_transfer(client->adapter, &msg, );
if (status == )
status = count; dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",
count, offset, status, jiffies); if (status == count)
return count;
} return -ETIMEDOUT;
}

3. eeprom测试

不同频率总线,对应的速率通过read较好体现;编写测试程序进行速率验证,中间经过文件系统一些限制。

3.1 测试预期

在at24.c文件中有关于不同速率测试预期,测试读要比写更纯粹一点,因为写只能一个字节一个自己,每次写完地址,再写一个字节。

/*
* This parameter is to help this driver avoid blocking other drivers out
* of I2C for potentially troublesome amounts of time. With a 100 kHz I2C
* clock, one 256 byte read takes about 1/43 second which is excessive;
* but the 1/170 second it takes at 400 kHz may be quite reasonable; and
* at 1 MHz (Fm+) a 1/430 second delay could easily be invisible.
*
* This value is forced to be a power of two so that writes align on pages.
*/

可以看出一次读256字节,1MHz速率耗时2.3ms;400KHz速率耗时5.9ms;100KHz速率耗时23.3ms。

修改io_limit可以改变一次io字节数。

3.2 测试程序

源代码如下,编译后执行“”teeprom /sys/bus/i2c/devices/1-0050/eeprom value count”即可:

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/i2c-dev.h>
#include <errno.h>
#include <time.h> //teeprom device value size
#define EEPROM_SIZE 262144
#define PAGE_SIZE 4096
#define EEPROM_PAGES (EEPROM_SIZE/PAGE_SIZE)
int main(int argc, char *argv[])
{
int num, value;
int fd, pFile, page_index = 0;
char *device_name, *buff, *out_buf;
struct timespec time_0, time_1, time_2; printf("Please input as:");
printf("teeprom device_name value size\n");
fflush(stdout); if(argc < 3){
printf("arg error\n");
return -1;
} device_name = argv[1];
value = atoi(argv[2]);
num = atoi(argv[3]); buff = calloc(sizeof(char), num);
if(buff < 0){
printf("alloc failed\n");
return -1;
}
memset(buff, value, num); out_buf = calloc(sizeof(char), EEPROM_SIZE); fd = open(device_name,O_RDWR);
if(fd < 0){
printf("device open failed\n");
return -1;
}
clock_gettime(CLOCK_MONOTONIC, &time_0);
printf("%8d.%8d Write %d to 0x00 for %d bytes.\n", time_0.tv_sec, time_0.tv_nsec, value, num);
write(fd, buff, num);----------------------------------虽然此处希望写入num个字节,但是在系统调用到i2c写入中间有些限制了大小。 clock_gettime(CLOCK_MONOTONIC, &time_1);
printf("%8d.%8d Read from 0x00 for %d bytes.\n", time_1.tv_sec, time_1.tv_nsec, EEPROM_SIZE);
lseek(fd, 0, SEEK_SET);
for(page_index = 0; page_index < EEPROM_PAGES; page_index++)
{
read(fd, out_buf, PAGE_SIZE);----------------------对于读也同样的有限制,只能循环读page大小。
}
close(fd); clock_gettime(CLOCK_MONOTONIC, &time_2);
printf("%8d.%8d Write to eeprom.bin.\n", time_2.tv_sec, time_2.tv_nsec);
pFile = fopen("eeprom.bin","wb");
if(pFile < 0){
printf("device open failed\n");
return -1;
}
fwrite(out_buf,EEPROM_SIZE,1,pFile);
fclose(pFile); return 0;
}

3.3 结果分析

eeprom对应的nvmem节点如下,可以直接对其open/read/write/close操作。

/sys/bus/i2c/devices/1-0050/eeprom

/sys/bus/i2c/devices/3-0050/eeprom

对eeprom进行读写的backtrace如下:

#  at24_eeprom_read_i2c (at24=0xbda6b18c, buf=0xbdbc4380 '\177' <repeats  times>..., offset=, count=) at drivers/misc/eeprom/at24.c:
# 0x802673fe in at24_read (priv=0xbda6b18c, off=, val=0x380, count=) at drivers/misc/eeprom/at24.c:
# 0x803847f0 in nvmem_reg_read (nvmem=<optimized out>, nvmem=<optimized out>, bytes=<optimized out>, val=<optimized out>, offset=<optimized out>) at drivers/nvmem/core.c:
# bin_attr_nvmem_read (filp=<optimized out>, kobj=<optimized out>, attr=<optimized out>, buf=<optimized out>, pos=, count=) at drivers/nvmem/core.c:
# 0x800fc0b2 in sysfs_kf_bin_read (of=<optimized out>, buf=<optimized out>, count=<optimized out>, pos=-) at fs/sysfs/file.c:
# 0x800fb808 in kernfs_file_direct_read (ppos=<optimized out>, count=<optimized out>, user_buf=<optimized out>, of=<optimized out>) at fs/kernfs/file.c:
# kernfs_fop_read (file=<optimized out>, user_buf=0xbdbc4380 '\177' <repeats times>..., count=<optimized out>, ppos=0xc80) at fs/kernfs/file.c:
# 0x800a9436 in __vfs_read (file=0xbda6b18c, buf=<optimized out>, count=<optimized out>, pos=0xc80) at fs/read_write.c:
# 0x800a9fe8 in vfs_read (file=0xbdb2ed20, buf=0xbdbc4380 '\177' <repeats times>..., count=, pos=0xbdb71f44) at fs/read_write.c:
# 0x800aad02 in SYSC_read (count=<optimized out>, buf=<optimized out>, fd=<optimized out>) at fs/read_write.c:
# SyS_read (fd=<optimized out>, buf=, count=) at fs/read_write.c:
# 0x80020b40 in csky_systemcall () at arch/csky/kernel/entry.S:
# 0x2ac20008 in ?? () # at24_eeprom_write_i2c (at24=0xbda6b18c, buf=0xbdb9a2c0 "\177", offset=, count=) at drivers/misc/eeprom/at24.c:
# 0x8026739e in at24_write (priv=0xbda6b18c, off=, val=0x0, count=) at drivers/misc/eeprom/at24.c:
# 0x80384898 in nvmem_reg_write (nvmem=<optimized out>, nvmem=<optimized out>, bytes=<optimized out>, val=<optimized out>, offset=<optimized out>) at drivers/nvmem/core.c:
# bin_attr_nvmem_write (filp=<optimized out>, kobj=<optimized out>, attr=<optimized out>, buf=<optimized out>, pos=, count=) at drivers/nvmem/core.c:
# 0x800fc1a6 in sysfs_kf_bin_write (of=<optimized out>, buf=<optimized out>, count=<optimized out>, pos=-) at fs/sysfs/file.c:
# 0x800fb706 in kernfs_fop_write (file=<optimized out>, user_buf=<optimized out>, count=, ppos=0xbdb71f44) at fs/kernfs/file.c:
# 0x800a9506 in __vfs_write (file=0xbda6b18c, p=<optimized out>, count=<optimized out>, pos=0x1) at fs/read_write.c:
# 0x800aa106 in vfs_write (file=0xbdb2ed20, buf=0xbdb9a2c0 "\177", count=<optimized out>, pos=0xbdb71f44) at fs/read_write.c:
# 0x800aad9e in SYSC_write (count=<optimized out>, buf=<optimized out>, fd=<optimized out>) at fs/read_write.c:
# SyS_write (fd=<optimized out>, buf=, count=) at fs/read_write.c:
# 0x80020b40 in csky_systemcall () at arch/csky/kernel/entry.S:
# 0x0000c158 in ?? ()

csky_systemcall()->SyS_read()->SYSC_read()->vfs_read()->__vfs_read()->kernfs_fop_read()->kernfs_file_direct_read()->sysfs_kf_bin_read()->bin_attr_nvmem_read()->nvmem_reg_read()->at24_read()->at24_eeprom_read_i2c()

其中kernfs_file_direct_read()对读的大小进行了限制:

static ssize_t kernfs_file_direct_read(struct kernfs_open_file *of,
char __user *user_buf, size_t count,
loff_t *ppos)
{
ssize_t len = min_t(size_t, count, PAGE_SIZE);-------------------------可以看出实际读大小取count和PAGE_SIZE小者。
...
of->event = atomic_read(&of->kn->attr.open->event);
ops = kernfs_ops(of->kn);
   if (ops->read)
     len = ops->read(of, buf, len, *ppos);
  else
     len = -EINVAL;
...
}

到at24这一层,at24_eeprom_read_i2c()又对read进行了限制,每次只能读取最大io_limit个字节。

at24_read()中循环读取。

cky_systemcall()->SyS_write()->SYSC_write()->vfs_write()->__vfs_write()->kernfs_fop_write()->sysfs_kf_bin_write()->bin_attr_nvmem_write()->nvmem_reg_write()->at24_write()->at24_eeprom_write_i2c()

写实际上也限制了大小:

static ssize_t kernfs_fop_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
...
if (of->atomic_write_len) {
len = count;
if (len > of->atomic_write_len)
return -E2BIG;
} else {
len = min_t(size_t, count, PAGE_SIZE);-----------------------------此处可以看出实际写大小取count和PAGE_SIZE小者。
}
...
ops = kernfs_ops(of->kn);
if (ops->write)
len = ops->write(of, buf, len, *ppos);
else
len = -EINVAL;
...
}

at24_eeprom_write_i2c()中每次只能写入一个字节,at24_write()循环写入。

100KHz读256字节大概23.3ms,下面实际速度在22ms左右,符合预期。

[ 1105.099343] at24 -: read @ -->  ()
[ 1105.121371] at24 -: read @ --> ()
[ 1105.143395] at24 -: read @ --> ()
[ 1105.165428] at24 -: read @ --> ()

400KHz读256字节大概在5.9ms,下面实际速度在6.25ms,符合预期。

[ 3616.209011] at24 -: read @ -->  ()
[ 3616.215264] at24 -: read @ --> ()
[ 3616.221512] at24 -: read @ --> ()
[ 3616.227760] at24 -: read @ --> ()
[ 3616.234007] at24 -: read @ --> ()

在实际测试中,虽然上层希望读写一大块内容,但是底层对其进行了几次拆分。

实际结果可能会合期望有较大差别,这时候就需要跟踪读写流程。

参考文档:《I2C子系统之at24c02读写测试

AT24 I2C EEPROM解析及测试的更多相关文章

  1. AM335x kernel 4.4.12 i2c eeprom AT24c02驱动移植

    kernel 4.4.12 i2c eeprom AT24c02驱动移植 在kernel make menuconfig ARCH=ARM 中打开: Device Drivers ---> Mi ...

  2. iOS5系统API和5个开源库的JSON解析速度测试

    iOS5系统API和5个开源库的JSON解析速度测试 iOS5新增了JSON解析的API,我们将其和其他五个开源的JSON解析库进行了解析速度的测试,下面是测试的结果和工程代码附件. 我们选择的测试对 ...

  3. 机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理、源码解析及测试

    机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理.源码解析及测试 关键字:决策树.python.源码解析.测试作者:米仓山下时间:2018-10-2 ...

  4. dotnet core TargetFramework 解析顺序测试

    dotnet core TargetFramework 解析顺序测试 Intro 现在 dotnet 的 TargetFramework 越来越多,抛开 .NET Framework 不谈,如果一个类 ...

  5. 分布式监控系统开发【day38】:报警自动升级代码解析及测试(八)

    一.报警自动升级代码解析 发送邮件代码 def action_email(self,action_obj,action_operation_obj,host_id,trigger_data): ''' ...

  6. maven解析xml+测试test+注解

    条件:maven项目 测试图: 创建maven项目,在maven项目中scr目录下有main.test(没有就创建) 一.解析XML文件方式 在main目录下有java.resources.webap ...

  7. junit源码解析--捕获测试结果

    OK,前面的博客我们整理了junit运行完了所有的测试用例,那么OK了,现在开始该收集测试结果了. 在这最后一步中,junit主要是玩一个类,TestResult.这里类中封装了几个参数,在初始化这个 ...

  8. i2c 协议解析

    1.基本概念 主机            初始化发送,产生时钟信号和终止发送的器件 从机            被主机寻址的器件 发送器        发送数据到总线的器件 接收器        从总 ...

  9. i2c 协议解析【转】

    转自:http://blog.csdn.net/g_salamander/article/details/8016698 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.基本概念 主机    ...

随机推荐

  1. leetcode — construct-binary-tree-from-preorder-and-inorder-traversal

    import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * * Source : https:/ ...

  2. 设计模式总结篇系列:代理模式(Proxy)

    时代在发展,我们发现,现在不少明星都开始进行微访谈之类的,有越来越多的参与捐赠等.新的一天开始了,首先看下新的一天的日程安排: interface Schedule{ public void weiT ...

  3. Java多线程概念简介 多线程中篇(一)

    Java的线程与操作系统的线程   在线程的相关介绍中,有讲到“线程的实现”分为三种:内核支持,用户级以及两者混合.(这只是一种简要的分类) Java线程在JDK1.2之前,是用户线程实现的 而在JD ...

  4. 第46章 发现端点(Discovery Endpoint) - Identity Server 4 中文文档(v1.0.0)

    发现端点可用于检索有关IdentityServer的元数据 - 它返回发布者名称,密钥材料,支持的范围等信息.有关详细信息,请参阅规范. 发现端点可通过/.well-known/openid-conf ...

  5. Java高并发--原子性可见性有序性

    Java高并发--原子性可见性有序性 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 原子性:指一个操作不可中断,一个线程一旦开始,直到执行完成都不会被其他线程干扰.换 ...

  6. 快速数论变换(NTT)小结

    NTT 在FFT中,我们需要用到复数,复数虽然很神奇,但是它也有自己的局限性--需要用double类型计算,精度太低 那有没有什么东西能够代替复数且解决精度问题呢? 这个东西,叫原根 原根 阶 若\( ...

  7. 【20190305】CSS-响应式图片:srcset+sizes,picture,svg

    响应式图片可以根据不同的设备屏幕大小从而选择加载不同的图片,从而节省带宽.实现响应式图片有三种方法:srcset+sizes属性.picture标签.svg 1. srcset+sizes srcse ...

  8. JVM内存管理 《深入分析java web 技术内幕》第八章

    8.1 物理内存与虚拟内存 物理内存RAM(随机存储器),寄存单元为寄存器,用于存储计算单元执行指令的中间结果. 连接处理器和RAM或者处理器和寄存器的是地址总线,这个地址的宽度影响了物理地址的索引范 ...

  9. 初级c++编码规范

        想了很久,第一篇文章还是应该写编码规范好一点.编码规范是一个仁者见仁的问题,为了避免复杂庞大,自己总结了一套简单版本的规范. 简介     本文介绍一份自己使用的C++编码规范.第一次正式进入 ...

  10. PJProject(2.6) 工程介绍

    pjlib pjlib\build\pjlib.vcproj pjlib_test pjlib\build\pjlib_test.vcproj pjsip_core pjsip\build\pjsip ...