Linux 内核:I2C子系统分析(1)基于子系统的驱动分析与实现

背景

在学习高通平台的有关知识,看到一篇博客中介绍了GPIO模拟I2C设备,觉得挺有意思的。

看了看有关的实现,发现自己之前学习从Linux中的I2C子系统还没整理完。

原文的基础上做了补充。

系列:

描述

上文我们讲到了Linux 内核:I2C子系统分析(0)整体框架介绍,介绍了Linux i2c子系统的构成:

i2c子系统中也使用一个对象来描述一个物理实体,设备对象与驱动分离,驱动结合设备对象对硬件设备的描述才可以驱动一个具体的物理设备,体现了分离的设计思想,实现了代码的复用,比如:

  • 一个i2c控制器就对应一个i2c_board_info,它驱动就是s3c2410_i2c_driver,他们通过platform_bus_type协调工作。
  • 一个i2c总线上的设备就对应内核中的一个i2c_client类型的对象,它的驱动就是的i2c_driver,二者通过i2c_bus_type协调工作。
  • 同样是抽象的思路,对于i2c总线本身,内核也使用i2c_bus_type来描述。

事实上,对于任何一种总线,内核都有一个bus_type类型的对象与之对应。

但是platform_bus_type并没有对应的实际的物理总线,这也就是platform总线也叫虚拟总线的原因。

除了分离,i2c子系统也体现的软件分层的设计思想,整个i2c子系统由3层构成:设备驱动层--i2c核心--控制器驱动

核心结构

i2c_adapter对象实现了一组通过一个i2c控制器发送消息的所有信息,包括时序,地址等等, 即封装了i2c控制器的"控制信息"。它被i2c主机驱动创建,通过clien域和i2c_client和i2c_driver相连, 这样设备端驱动就可以通过其中的方法以及i2c物理控制器来和一个i2c总线的物理设备进行交互

i2c_algorithm描述一个i2c主机的发送时序的信息,该类的对象algo是i2c_adapter的一个域,其中的master_xfer()注册的函数最终被设备驱动端的i2c_transfer()回调。

i2c_client描述一个挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver以及i2c_adapter相连,在控制器驱动与控制器设备匹配成功后被控制器驱动通过i2c_new_device()创建。

i2c_driver描述一个挂接在硬件i2c总线上的设备的驱动方法,即i2c设备的驱动对象,通过i2c_bus_type和设备信息i2c_client匹配,匹配成功后通过clients和i2c_client对象以及i2c_adapter对象相连

i2c_msg描述一个在设备端和主机端之间进行流动的数据,在设备驱动中打包并通过i2c_transfer()发送。相当于skbuf之于网络设备,urb之于USB设备。

核心方法

i2c_transfer()是i2c核心提供给设备驱动的发送方法,通过它发送的数据需要被打包成i2c_msg,这个函数最终会回调相应i2c_adapter->i2c_algorithm->master_xfer()接口将i2c_msg对象发送到i2c物理控制器

核心结构与方法详述

i2c_adapter

并不是编写一个i2c设备驱动需要实现这一层的工作。

通常我们在配置内核的时候已经将i2c控制器的设备信息和驱动已经编译进内核了,就是这个adapter对象已经创建好了, 但是了解其中的成员对于理解i2c驱动框架非常重要, 所有的设备驱动都要经过这个对象的处理才能和物理设备通信

//include/linux/i2c.h
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data; /* data fields that are valid for all devices */
struct rt_mutex bus_lock; int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */ int nr;
char name[48];
struct completion dev_released; struct mutex userspace_clients_lock;
struct list_head userspace_clients; struct i2c_bus_recovery_info *bus_recovery_info;
};

struct i2c_adapter

  • algo:这个i2c控制器需要的控制算法,其中最重要的成员是master_xfer()接口,这个接口是硬件相关的,里面的操作都是基于具体的SoCi2c寄存器的,它将完成将数据发送到物理i2c控制器的"最后一公里"
  • dev:表示这个一个device,会挂接到内核中的链表中来管理
  • userspace_clients:这个节点将一个i2c_adapter对象和它所属的i2c_client对象以及相应的i2c_driver对象连接到一起

下面是2个i2c-core.c提供的i2c_adapter直接相关的操作API,通常也不需要设备驱动开发中使用:

  • i2c_add_adapter
  • i2c_del_adapter

i2c_add_adapter

这个API可以将一个i2c_adapter类型的对象注册到内核中,源码我就不贴了,下面是他们的调用关系,我们可以从中看到一个adapter对象和系统中的i2c_driver对象以及i2c_client对象的匹配流程。

首先,我们在驱动中构造一个i2c_adapter对象的时候,对其中的相关域进行初始化,这里我们最关心它的父设备

//drivers/i2c/buses/i2c-s3c2410.c
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
i2c->adap.dev.parent = &pdev->dev;
}

得到了这样一个i2c_adapter对象,我们就可以调用这个API将它注册到内核,调用关系如下:

▲i2c_add_adapter()▲
01 └──i2c_register_adapter(adapter)
02 ├──adap->dev.bus = &i2c_bus_type;
03 ├──adap->dev.type = &i2c_adapter_type;
04 │ └──i2c_adapter_attr_groups
05 │ └── i2c_adapter_attr_group
06 │ └── i2c_adapter_attrs
07 │ └── &dev_attr_new_device.attr
08 │ └──DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);
09 │ └──i2c_sysfs_new_device()
10 │ └──▲list_add_tail(&client->detected, &adap->userspace_clients);▲
11 └──device_register(&adap-dev);
12 ├──device_initialize(dev);
13 │ ├──/* /sys/devices/ */
14 │ ├──struct kset *devices_kset;
15 │ ├──dev->kobj.kset = devices_kset;
16 │ ├──kobject_init(&dev->kobj, &device_ktype);
17 │ └──set_dev_node(dev, -1);
18 └──device_add(dev);
19 ├──parent=get_device(dev->parent);
20 ├──▲kobj = get_device_parent(dev, parent);▲
21 │ └──return &parent->kobj;
22 ├──▲dev->kobj.parent = kobj;▲
23 ├──set_dev_node(dev, dev_to_node(parent));
24 ├──kobject_add(&dev->kobj, dev->kobj.parent, NULL);
25 │ ├──kobject_add_varg(kobj, parent, fmt, args);
26 │ ├──kobj->parent = parent;
27 │ ├──kobject_add_internal(kobj);
28 │ ├──parent = kobject_get(kobj->parent);
29 │ ├──kobj_kset_join(kobj);
30 │ │ ├──kset_get(kobj->kset)
31 │ │ └──▲list_add_tail(&kobj->entry, &kobj->kset->list); ▲
32 │ ├──kobj->parent = parent;
33 │ └──create_dir(kobj);
34 ├──device_create_file(dev, &dev_attr_uevent);
35 ├──device_create_sys_dev_entry(dev);
36 ├──devtmpfs_create_node(dev);
37 ├──device_add_class_symlinks(dev);
38 ├──device_add_attrs(dev);
39 ├──bus_add_device(dev);
40 ├──bus_probe_device(dev);
41 ├──klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children);
42 └──klist_add_tail(&dev->knode_class,&dev->class->p->klist_devices);

我们可以看出,

  • --10-->将i2c_adapter对象中的userspace_clients与匹配到的client对象中detected连接到一起
  • --15-->将/sys/devices的kset赋值给i2c_adapter->device->kobject->parent,即建立i2c_adapter对象和/sys/devices的父子关系, 参见"Linux设备管理(一)_kobject, kset,ktype分析"
  • --20-->获取device->parent的kobject对象
  • --22-->将device->parent的kobject对象作为device->kobject->parent,形成device->kobject->parent == device->parent->kobject
  • --31-->将这个device->kobject挂接到device->kset->list链表中,由此可见,kobject->kset指向的kset对象和kobject->entry挂接到的kset对象可以不是一个,与"Linux设备管理(一)_kobject, kset,ktype分析"那种情况不同.

i2c_del_adapter

从内核中删除一个adapter

i2c_client

在i2c设备端,驱动开发的主要工作和平台总线一样:构建设备对象和驱动对象,我用的开发板上的i2c总线上挂接的设备是mpu6050,接下来我就以我的板子为例,讨论如何编写i2c设备端的驱动。

同样这里的设备对象也可以使用三种方式构建:平台文件,模块和设备树。

本文采用设备树的方式构建设备对象,我们可以参考内核文档"Documentations/devicetree/bindings/i2c/i2c-s3c2410.txt"以及设备树中的样板来编写我们的设备树节点。

/{
i2c@138B0000 { // SoC上的i2c控制器的地址
#address-cells = <1>;
#size-cells = <0>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <20000>;
pinctrl-0 =<&i2c5_bus>;
pinctrl-names="default";
status="okay"; // 使能
mpu6050@68{ // 子节点,代表 i2c从设备,68即是设备地址,通过查阅i2c设备的数据手册可知
compatible="invensense,mpu6050"; // 驱动标识,用于匹配对应的驱动
reg=<0x68>; // 68即是设备地址,同 @68
};
};
};

写了这个设备节点,内核就会为我们在内核中构造一个i2c_client对象并挂接到i2c总线对象的设备链表中以待匹配,这个设备类如下

//include/linux/i2c.h
struct i2c_client {
unsigned short flags; /* div., see below */ // 设备地址
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE]; // 表示这个client从属的i2c主机对应的adapter对象,驱动方法中使用这个指针发送数据
struct i2c_adapter *adapter; /* the adapter we sit on */ // 设备
struct device dev; /* the device structure */ // 所使用的中断号
int irq; /* irq issued by device */ // i2c client 链条入口(老内核链表了)
struct list_head detected;
};

i2c_driver

和平台总线类似,i2c驱动对象使用i2c_driver结构来描述,所以,编写一个i2c驱动的本质工作就是构造一个i2c_driver对象并将其注册到内核。我们先来认识一下这个对象

//include/linux/i2c.h
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
// 探测函数,匹配成功之后执行,会将匹配到的i2c_client对象传入,完成申请资源,初始化,提供接口等工作。
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
// 移除函数,设备消失时会调用,驱动模块被rmmod时也会先被调用,完成和probe相反的操作。
int (*remove)(struct i2c_client *);
// 电源管理相关
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *); void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); // 表明这是一个设备的驱动类,和platform一样,**用于匹配设备树的of_match_table域在这里**
struct device_driver driver;
// 用于使用平台文件或模块编写设备信息时进行匹配使用,相当于platform_driver中的id_table。
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};

那么接下来就是填充对象了,我们这里使用的是设备树匹配,所以of_match_table被填充如下。

struct of_device_id mpu6050_dt_match[] = {
{.compatible = "invensense,mpu6050"},
{},
};
struct i2c_device_id mpu6050_dev_match[] = {};

然后将这两个成员填充到i2c_driver对象如下,这个阶段我们可以在mpu6050_probe中只填写prink来测试我们的驱动方法对象是否有问题。

struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050drv",
.of_match_table = of_match_ptr(mpu6050_dt_match),
},
.id_table = mpu6050_dev_match,
};

使用下述API注册/注销驱动对象,这个宏和module_platform_driver一样是内核提供给我们一个用于快速实现注册注销接口的快捷方式,写了这句以及模块授权,我们就可以静待各种信息被打印了

module_i2c_driver(mpu6050_driver);

i2c_msg

如果测试通过,我们就要研究如何找到adapter以及如何通过找到的adapter将数据发送出去。没错,我说的i2c_msg

struct i2c_msg {
// 从机地址
__u16 addr; /* slave address */ // 操作标志,I2C_M_RD为读(1),写为0
__u16 flags;
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ // 有效数据长度
__u16 len; /* msg length */ // 装载有效数据的头指针
__u8 *buf; /* pointer to msg data */
};

我们知道,i2c总线上传入数据是以字节为单位的,而我们的通信类别分为两种:

对于写,通常按照下面的时序:

Mater S I2CAddr+WriteBit InternalRegisterAddr DATA DATA P
Slave ACK ACK ACK ACK

对于读,通常是按照下面的时序:

Mater S I2CAddr+WriteBit InternalRegisterAddr S I2CAddr+ReadBit ACK NACK P
Slave ACK ACK ACK DATA DATA

i2c子系统为了实现这种通信方法,为我们封装了i2c_msg结构。对于每一个START信号,都对应一个i2c_msg对象

实际操作中我们会将所有的请求封装成一个struct i2c_msg[],一次性将所有的请求通过i2c_transfer()发送给匹配到的client的从属的adapter,由adapter根据相应的algo域以及master_xfer域通过主机驱动来将这些请求发送给硬件上的设备。

实例

这是一个通过i2c总线来访问mpu6050的驱动

mpu6050_common.h

#define MPU6050_MAGIC 'K'

union mpu6050_data
{
struct {
short x;
short y;
short z;
}accel;
struct {
short x;
short y;
short z;
}gyro;
unsigned short temp;
}; #define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union mpu6050_data)
#define GET_GYRO _IOR(MPU6050_MAGIC, 1, union mpu6050_data)
#define GET_TEMP _IOR(MPU6050_MAGIC, 2, union mpu6050_data)

mpu6050_drv.h

#define	SMPLRT_DIV		0x19	//陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波,典型值:0x18(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47 //陀螺仪z轴角速度数据寄存器(高位)
#define GYRO_ZOUT_L 0x48 //陀螺仪z轴角速度数据寄存器(低位)
#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
#define SlaveAddress 0x68 //MPU6050-I2C地址寄存器
#define W_FLG 0
#define R_FLG 1

mpu6050.c

注意,client在fdt中就已经存在,probe这个驱动时才绑定到这里来的。

因此,看不到client参数是怎么来的。

#define DEV_CNT 1
#define DEV_MI 0
#define DEV_MAME "mpu6050" struct mpu6050_pri {
struct cdev dev;
struct i2c_client *client;
};
struct mpu6050_pri dev; struct class *cls;
dev_t dev_no ; static void mpu6050_write_byte(struct i2c_client *client,const unsigned char reg,const unsigned char val)
{
char txbuf[2] = {reg,val};
struct i2c_msg msg[2] = {
[0] = {
.addr = client->addr,
.flags= W_FLG,
.len = sizeof(txbuf),
.buf = txbuf,
},
};
i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
} static char mpu6050_read_byte(struct i2c_client *client,const unsigned char reg)
{
char txbuf[1] = {reg};
char rxbuf[1] = {0};
struct i2c_msg msg[2] = {
[0] = {
.addr = client->addr,
.flags = W_FLG,
.len = sizeof(txbuf),
.buf = txbuf,
},
[1] = {
.addr = client->addr,
.flags = I2C_M_RD,
.len = sizeof(rxbuf),
.buf = rxbuf,
},
}; i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
return rxbuf[0];
} static long dev_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
int res = 0;
union mpu6050_data data = {{0}};
switch(cmd){
case GET_ACCEL:
data.accel.x = mpu6050_read_byte(dev.client,ACCEL_XOUT_L);
data.accel.x|= mpu6050_read_byte(dev.client,ACCEL_XOUT_H)<<8;
data.accel.y = mpu6050_read_byte(dev.client,ACCEL_YOUT_L);
data.accel.y|= mpu6050_read_byte(dev.client,ACCEL_YOUT_H)<<8;
data.accel.z = mpu6050_read_byte(dev.client,ACCEL_ZOUT_L);
data.accel.z|= mpu6050_read_byte(dev.client,ACCEL_ZOUT_H)<<8;
break;
case GET_GYRO:
data.gyro.x = mpu6050_read_byte(dev.client,GYRO_XOUT_L);
data.gyro.x|= mpu6050_read_byte(dev.client,GYRO_XOUT_H)<<8;
data.gyro.y = mpu6050_read_byte(dev.client,GYRO_YOUT_L);
data.gyro.y|= mpu6050_read_byte(dev.client,GYRO_YOUT_H)<<8;
data.gyro.z = mpu6050_read_byte(dev.client,GYRO_ZOUT_L);
data.gyro.z|= mpu6050_read_byte(dev.client,GYRO_ZOUT_H)<<8;
printk("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
break;
case GET_TEMP:
data.temp = mpu6050_read_byte(dev.client,TEMP_OUT_L);
data.temp|= mpu6050_read_byte(dev.client,TEMP_OUT_H)<<8;
printk("temp: %d\n",data.temp);
break;
default:
printk(KERN_INFO "invalid cmd");
break;
}
printk("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z);
res = copy_to_user((void *)arg,&data,sizeof(data));
return sizeof(data);
} struct file_operations fops = {
.unlocked_ioctl = dev_ioctl,
}; static void mpu6050_init(struct i2c_client *client)
{
mpu6050_write_byte(client, PWR_MGMT_1, 0x00);
mpu6050_write_byte(client, SMPLRT_DIV, 0x07);
mpu6050_write_byte(client, CONFIG, 0x06);
mpu6050_write_byte(client, GYRO_CONFIG, 0x18);
mpu6050_write_byte(client, ACCEL_CONFIG, 0x0);
} static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
dev.client = client;
printk(KERN_INFO "xj_match ok\n");
cdev_init(&dev.dev,&fops); alloc_chrdev_region(&dev_no,DEV_MI,DEV_CNT,DEV_MAME); cdev_add(&dev.dev,dev_no,DEV_CNT); mpu6050_init(client); /*创建设备文件*/
cls = class_create(THIS_MODULE,DEV_MAME);
device_create(cls,NULL,dev_no,NULL,"%s%d",DEV_MAME,DEV_MI); printk(KERN_INFO "probe\n"); return 0;
} static int mpu6050_remove(struct i2c_client * client)
{
device_destroy(cls,dev_no);
class_destroy(cls);
unregister_chrdev_region(dev_no,DEV_CNT);
return 0;
} struct of_device_id mpu6050_dt_match[] = {
{.compatible = "invensense,mpu6050"},
{},
}; struct i2c_device_id mpu6050_dev_match[] = {};
struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050drv",
.of_match_table = of_match_ptr(mpu6050_dt_match),
},
.id_table = mpu6050_dev_match,
};
module_i2c_driver(mpu6050_driver);
MODULE_LICENSE("GPL");

通过上面的驱动,我们可以在应用层操作设备文件从mpu6050寄存器中读取原始数据,应用层如下

#include ...

int main(int argc, char * const argv[])
{
int fd = open(argv[1],O_RDWR);
if(-1== fd){
perror("open");
return -1;
} union mpu6050_data data = {{0}}; while(1){
ioctl(fd,GET_ACCEL,&data);
printf("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z);
ioctl(fd,GET_GYRO,&data);
printf("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
ioctl(fd,GET_TEMP,&data);
printf("temp: %d\n",data.temp);
sleep(1);
}
return 0;
}

总结

IIC总线虽然并不复杂,但linux下的IIC子系统却有相当的复杂度。做了很多的抽象工作,代码变得得难理解,但为后续移植驱动工作提供了很多便利条件。

Linux 内核:I2C子系统分析(1)基于子系统的驱动分析与实现的更多相关文章

  1. linux内核I2C子系统学习(三)

    写设备驱动: 四部曲: 构建i2c_driver 注册i2c_driver 构建i2c_client ( 第一种方法:注册字符设备驱动.第二种方法:通过板文件的i2c_board_info填充,然后注 ...

  2. 嵌入式Linux内核I2C子系统详解

    1.1 I2C总线知识 1.1.1  I2C总线物理拓扑结构     I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成.通信原理是通过对SCL和SDA线高 ...

  3. Linux内核3.0移植并基于Initramfs根文件系统启动

    Linux内核移植与启动 Target borad:FL2440 Bootloader:U-boot-2010.09 交叉编译器:buildroot-2012.08 1.linux内核基础知识 首先, ...

  4. [置顶] linux内核启动2-setup_arch中的内存初始化(目前分析高端内存)

    上一篇微博留下了这几个函数,现在我们来分析它们         sanity_check_meminfo();         arm_memblock_init(&meminfo, mdes ...

  5. 《linux 内核全然剖析》sched.c sched.h 代码分析笔记

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/u011368821/article/details/25129835 sched.c sched.h ...

  6. 基于335X的Linux网口驱动分析

    基于335X的linux网口驱动分析 一. 系统构成 1.  硬件平台 AM335X 2.  LINUX内核版本 4.4.12 二. 网口驱动构架(mdio部分) mdio网口驱动部分 使用 总线.设 ...

  7. Linux内核内存管理架构

    内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射.页面分配.页面回收.页面交换.冷热页面.紧急页面.页面碎片管理.页面缓存.页面统计等,而且对性能也有很高的要 ...

  8. 模仿Linux内核kfifo实现的循环缓存

    想实现个循环缓冲区(Circular Buffer),搜了些资料多数是基于循环队列的实现方式.使用一个变量存放缓冲区中的数据长度或者空出来一个空间来判断缓冲区是否满了.偶然间看到分析Linux内核的循 ...

  9. Linux内核学习总结

    1.<简单C程序生成的汇编代码分析> http://www.cnblogs.com/snowfox2016/p/5225937.html 2.<时间片轮转多道程序代码分析>(未 ...

  10. linux驱动基础系列--Linux 串口、usb转串口驱动分析

    前言 主要是想对Linux 串口.usb转串口驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如字符设备驱动.平台驱动等也不进行详细说明原理.如果有任何错误地方,请指出, ...

随机推荐

  1. fastreport .net打印普通报表

    fastreport .net打印普通报表 前言: .net代码层先不记录在这,后续会单独写一篇博客来记录. 直接在工具上进行功能点的实现 一.效果图 二.功能点 分页 分页小计 金额大写 三.功能点 ...

  2. 【GUI软件】小红书搜索结果批量采集,支持多个关键词同时抓取!

    目录 一.背景介绍 1.1 爬取目标 1.2 演示视频 1.3 软件说明 二.代码讲解 2.1 爬虫采集模块 2.2 软件界面模块 2.3 日志模块 三.获取源码及软件 一.背景介绍 1.1 爬取目标 ...

  3. 【Linux】XFS文件系统

    XFS WIKI介绍 XFS is a high-performance 64-bit journaling file system created by Silicon Graphics, Inc ...

  4. Linux中的umask

    在Linux中,当创建一个文件或者目录的时候,系统会自动为这个文件或者目录赋予默认的权限,而umask命令就是用来控制这个默认权限的. 查看umask umask的查看有两种方式,一种不带选项-S,一 ...

  5. debug技巧之本地调试

    大家好啊,我是summo,今天给大家分享一下我平时是怎么调试代码的,不是权威也不是教学,就是简单分享一下,如果大家还有更好的调试方式也可以多多交流哦. 如果看过我文章的同学应该知道我是一个Java开发 ...

  6. uniapp中正确使用echart

    uniapp中不能直接使用百度echart,要么就只能嵌入html,然后在html中进入echart进行使用,这样非常不方便, 下面介绍这个插件,对百度echart进行局部小改造,使他能在uniapp ...

  7. 【OpenVINO™】在 C# 中使用OpenVINO™ 部署PP-YOLOE实现物体检测

     前言 OpenVINO C# API 是一个 OpenVINO 的 .Net wrapper,应用最新的 OpenVINO 库开发,通过 OpenVINO C API 实现 .Net 对 OpenV ...

  8. 物联网平台选型葵花宝典:盘点开源、SaaS及通用型平台的优劣对比

    随着工业物联网领域和智慧物联领域的发展,大大小小的物联项目和物联场景需求层出不穷,物联网平台作为技术底座型软件,是不可或缺的项目地基. 市场需求下,物联网平台提供商越来越多,"打地基&quo ...

  9. FFmpeg开发笔记(二十一)Windows环境给FFmpeg集成AVS3解码器

    ​AVS3是中国AVS工作组制定的第三代音视频编解码技术标准,也是全球首个已推出的面向8K及5G产业应用的视频编码标准.AVS工作组于2019年3月9日完成第三代AVS视频标准(AVS3)基准档次的制 ...

  10. JavaScript的数学计算库:decimal.js

    An arbitrary-precision Decimal type for JavaScript. 功能 整数和浮点数 简单但功能齐全的 API 复制 JavaScript 和对象的许多方法Num ...