Linux Storage入门学习

前言

本文大量代码基于linux 0.11,因为早期linux的版本更加适合初学者入门。虽然代码比较早,但是不妨碍我们学习Linux Storage的精髓。

一、hello world

1.1 Demo

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main(int argc, char *argv[]) {printf("pid = %d\n", getpid());int fd1,fd2;char s[] = "hello world\n";//打开文件,拿到fdfd1 = open("/tmp/1.txt", O_RDWR | O_CREAT);printf("fd1 = %d\n", fd1);//写入write(fd1, s, sizeof(s));close(fd1);fd2 = open("/tmp/2.txt", O_RDWR | O_CREAT);printf("fd2 = %d\n", fd2);//打开文件,拿到fdfd1 = open("/tmp/1.txt", O_RDWR);printf("fd1 = %d\n", fd1);char buffer[80];//读取read(fd1, buffer, sizeof(buffer));//关闭fdprintf("%s", buffer);getchar();//暂停程序close(fd1);close(fd2);return 0;
}

运行结果

pid = 14378
fd1 = 3
fd2 = 3
fd1 = 4
hello world


1.1 fd只是一个数字,代表数字和文件之前的一个映射关系

查看/proc/14378/fd,可以看到映射关系

dr-x------ 2 wangbinhong tctnb  0 3月  14 16:27 .
dr-xr-xr-x 9 wangbinhong tctnb  0 3月  14 16:26 ..
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 0 -> /dev/pts/19 //stdin
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 1 -> /dev/pts/19 //stdout
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 2 -> /dev/pts/19 //stderr
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 3 -> /tmp/2.txt
lrwx------ 1 wangbinhong tctnb 64 3月  14 16:27 4 -> /tmp/1.txt

程序中的4是数字,/proc/14378/fd/4是一个文件,链接到/tmp/1.txt,/proc/14378/fd/4只存在内存中,不存在硬盘中,程序中的数字4不是指向/proc/14378/fd/4文件

二、数字fd代表什么

2.1 task_struct

每一个进程在内核中的有一个task_struct的结构体,结构体中有一个file指针数组filp,fd代表filp这个数组的下标,fd = 4 就代表filp[4]指向的file结构体

struct task_struct {...struct file * filp[NR_OPEN];...
}

2.2 file

struct file {unsigned short f_mode;//文件的类型和属性unsigned short f_flags;//文件打开的标志unsigned short f_count;//关联的fd的个数struct m_inode * f_inode;//file的真实实现off_t f_pos;//文件当前的读写指针,读到哪里了。
}

2.3 file_table

内核中还有一个全局的file_table,是file数组,保存所有file结构体。

struct file file_table[NR_FILE];//系统级的一个file table

2.4 关联关系

两个细节
dup:同进程两个fd指向同一个file
fork:两个进程的两个fd指向同一个file


下面是新的Kernel关系图


2.5 Binder传输fd

Binder传输fd,两个进程的不同fd指向了同一个file,享有相同的file offset和file status flag.

三、以pipe为例:一切皆文件

早期的错误想法

硬件驱动通过设备文件和用户空间的应用程序通信,是通过将驱动的信息写进设备文件,然后应用程序读取设备文件的内容。

3.1 pipe初始化


3.1.1 sys_pipe

系统调用pipe的实现,会返回两个fd给用户空间

int sys_pipe(unsigned long * fildes)//系统调用生成一对pipe
{struct m_inode * inode;struct file * f[2];int fd[2];int i,j;j=0;for(i=0;j<2 && i<NR_FILE;i++)if (!file_table[i].f_count)//找到空闲的file(f[j++]=i+file_table)->f_count++;//将空闲的file的f_count+1,并保存这两个file结构体if (j==1)//只找到一个f[0]->f_count=0;//将第一file重置,清空if (j<2)return -1;//没找到一队,反正就是失败j=0;for(i=0;j<2 && i<NR_OPEN;i++)//将两个file的指针分别保存到current->filp[i]的空闲处if (!current->filp[i]) {current->filp[ fd[j]=i ] = f[j];j++;}if (j==1)current->filp[fd[0]]=NULL;if (j<2) {f[0]->f_count=f[1]->f_count=0;return -1;}//和上面逻辑类似if (!(inode=get_pipe_inode())) {//详见3.1.2 获得一个pipe inodecurrent->filp[fd[0]] =current->filp[fd[1]] = NULL;f[0]->f_count = f[1]->f_count = 0;return -1;}f[0]->f_inode = f[1]->f_inode = inode;//让两个file结构体的f_inode指向pipe inodef[0]->f_pos = f[1]->f_pos = 0;//重置读写指针f[0]->f_mode = 1;       /* read */f[1]->f_mode = 2;       /* write */put_fs_long(fd[0],0+fildes);//将fd0数值返回给用户空间put_fs_long(fd[1],1+fildes);//将fd1数值返回给用户空间return 0;
}
3.1.2 get_pipe_inode

创建一个m_inode结构体
将m_inode的i_size指向一块4096B的缓冲区
设置m_inode的i_pipe为1,标识这个m_inode为pipe inode

struct m_inode * get_pipe_inode(void)//返回一个空的pipe inode用于pipe
{struct m_inode * inode;if (!(inode = get_empty_inode()))return NULL;if (!(inode->i_size=get_free_page())) {//申请一个物理页4096B作为环形管道缓冲区,缓冲区指针保存到i_size。inode->i_count = 0;return NULL;}inode->i_count = 2; /* sum of readers/writers */PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;inode->i_pipe = 1;//表示为pipe的m_inodereturn inode;
}
3.1.3 get_free_page

申请一个物理页,并返回内核空间的地址

unsigned long get_free_page(void)
{
register unsigned long __res asm("ax");__asm__("std ; repne ; scasb\n\t""jne 1f\n\t""movb $1,1(%%edi)\n\t""sall $12,%%ecx\n\t""addl %2,%%ecx\n\t""movl %%ecx,%%edx\n\t""movl $1024,%%ecx\n\t""leal 4092(%%edx),%%edi\n\t""rep ; stosl\n\t""movl %%edx,%%eax\n""1:":"=a" (__res):"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),"D" (mem_map+PAGING_PAGES-1):"di","cx","dx");
return __res;
}

3.2 读pipe

3.2.1 sys_read

根据inode->i_pip,将sys_read变成read_pipe。

int sys_read(unsigned int fd,char * buf,int count)//文件读的系统调用,fd->file->inode->数据块
{struct file * file;struct m_inode * inode;if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))return -EINVAL;if (!count)return 0;verify_area(buf,count);inode = file->f_inode;if (inode->i_pipe)//pipereturn (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;//3.2.2if (S_ISCHR(inode->i_mode))//字符设备return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);if (S_ISBLK(inode->i_mode))//块设备return block_read(inode->i_zone[0],&file->f_pos,buf,count);if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) {//常规文件或目录if (count+file->f_pos > inode->i_size)count = inode->i_size - file->f_pos;if (count<=0)return 0;return file_read(inode,file,buf,count);}printk("(Read)inode->i_mode=%06o\n\r",inode->i_mode);return -EINVAL;
}
3.2.2 read_pipe

将缓冲区的数据读到用户空间char,读count字节

int read_pipe(struct m_inode * inode, char * buf, int count)//读取pipe
{int chars, size, read = 0;while (count>0) {while (!(size=PIPE_SIZE(*inode))) {//如果发现没有内容wake_up(&inode->i_wait);//唤醒写端if (inode->i_count != 2) /* are there any writers? *///没有写端return read;sleep_on(&inode->i_wait);//没有内容就睡眠}chars = PAGE_SIZE-PIPE_TAIL(*inode);//判断尾部的数据if (chars > count)chars = count;if (chars > size)chars = size;count -= chars;read += chars;size = PIPE_TAIL(*inode);//头部开始读的指针PIPE_TAIL(*inode) += chars;PIPE_TAIL(*inode) &= (PAGE_SIZE-1);while (chars-->0)put_fs_byte(((char *)inode->i_size)[size++],buf++);}wake_up(&inode->i_wait);return read;
}

3.3 写pipe

3.3.1 sys_write

根据inode->i_pip,将sys_write变成write_pipe。

int sys_write(unsigned int fd,char * buf,int count)//文件写的系统调用,fd->file->inode->数据块
{struct file * file;struct m_inode * inode;if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd]))return -EINVAL;if (!count)return 0;inode=file->f_inode;if (inode->i_pipe)return (file->f_mode&2)?write_pipe(inode,buf,count):-EIO;//3.3.2if (S_ISCHR(inode->i_mode))return rw_char(WRITE,inode->i_zone[0],buf,count,&file->f_pos);if (S_ISBLK(inode->i_mode))return block_write(inode->i_zone[0],&file->f_pos,buf,count);if (S_ISREG(inode->i_mode))return file_write(inode,file,buf,count);printk("(Write)inode->i_mode=%06o\n\r",inode->i_mode);return -EINVAL;
}
3.3.2 write_pipe

将用户空间char对应的数据写到缓冲区,写入count字节

int write_pipe(struct m_inode * inode, char * buf, int count)//写pipe的实现
{int chars, size, written = 0;while (count>0) {while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {//如果写满了,size为0wake_up(&inode->i_wait);//唤醒读端if (inode->i_count != 2) { /* no readers *///没有读者直接返回current->signal |= (1<<(SIGPIPE-1));return written?written:-1;}sleep_on(&inode->i_wait);//写端休眠}chars = PAGE_SIZE-PIPE_HEAD(*inode);//计算管道头部到缓冲区末端的空闲字节数 4098if (chars > count)chars = count;if (chars > size)chars = size;count -= chars;written += chars;size = PIPE_HEAD(*inode);//当前的头指正PIPE_HEAD(*inode) += chars;PIPE_HEAD(*inode) &= (PAGE_SIZE-1);while (chars-->0)((char *)inode->i_size)[size++]=get_fs_byte(buf++);//一个写字符到管道}wake_up(&inode->i_wait);//写完唤醒读端return written;
}

3.4 pipe读写指针

其实pipe的读写指针并没有保存在file结构体,而是保存在m_inode。
如果缓冲区满了,读端一会不读,会导致写端的进程sleep。
整个读写的过程,并没有通过锁来控制,而是通过ringbuffer来实现,有兴趣的可以自己研究。

struct m_inode {
...unsigned long i_size;//被作为指针指向申请的缓冲区,一个缓冲区4096Bunsigned short i_zone[9];//用i_zone[0]代表写的游标,用i_zone[1]代表写的游标
...
}


3.5 思考一个问题

父进程创建一对pipe的fd1 fd2
子进程通过fork复制父进程的pipe fd1 fd2
父进程关闭读的fd1
子进程关闭写的fd2
父子进程就可以通过pipe进行跨进程通信


3.6 Linux的改进

当文件类型越来越多的时候,用file_operations结构体代替大量if else
file_operations中保存read write的函数指针

struct file {const struct file_operations    *f_op;void            *private_data;//这个很重要
}struct file_operations {ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
};

我觉得这样子理解更加合适:一切皆文件接口

四、普通文件

4.1重要数据结构

4.1.1 m_inode

file中f_inode指向就是m_inode,也就是file对应的真实实现。
i_dev:代表块设备号
i_zone[9]:代表数据块号

struct m_inode {unsigned short i_mode;//15-12文件类型,11-9保存执行文件设置,8-0保存文件权限unsigned short i_uid;//文件宿主的用户idunsigned long i_size;//文件长度unsigned long i_mtime;//修改时间unsigned char i_gid;//文件宿主的组idunsigned char i_nlinks;//硬链接的次数unsigned short i_zone[9];//对应数据块 0-6直接是块号,7一次间接块,8二次间接块,一个块是1KB=1024Byte//因为块号用short来表示,也就是2Byte,所以一个块可以存放512个块号,所以一次块512个,二次块就是512*512。//所以变相的可以算出一个文件的最大size是7+512+512*512 kb//一般逻辑块的大小会和buffer_head大小一样。
/* these are in memory also */struct task_struct * i_wait;unsigned long i_atime;unsigned long i_ctime;unsigned short i_dev;//设备号unsigned short i_num;unsigned short i_count;unsigned char i_lock;unsigned char i_dirt;unsigned char i_pipe;unsigned char i_mount;unsigned char i_seek;unsigned char i_update;
};


4.1.2 buffer_head

b_dev设备号
b_blocknr数据块号
b_data指向一块内存
所有buffer_head会被存放在一个hash表中

struct buffer_head {char * b_data;          /* pointer to data block (1024 bytes) */unsigned long b_blocknr;    /* block number */ //块号unsigned short b_dev;       /* device (0 = free) */ //设备号unsigned char b_uptodate;unsigned char b_dirt;       /* 0-clean,1-dirty */unsigned char b_count;      /* users using this block */unsigned char b_lock;       /* 0 - ok, 1 -locked */struct task_struct * b_wait;struct buffer_head * b_prev;struct buffer_head * b_next;struct buffer_head * b_prev_free;struct buffer_head * b_next_free;
};

可以调用下面两个接口,完成数据块的读写,这背后的实现就要看块设备驱动怎么实现的。

先记住,buffer_head(设备号+块号+内存地址)+读写指令 可以完成一次信息的交换。
ll_rw_block(READ,bh);
ll_rw_block(WRITE,bh);

4.2 读文件

4.2.1 file_read

根据(filp->f_pos)/BLOCK_SIZE计算对应的块号nr
根据inode->i_dev和nr调用bread获得buffer_head
调用ll_rw_block(READ,bh)请求数据块的数据
将buffer_head中b_data拷贝到用户空间的buf

int file_read(struct m_inode * inode, struct file * filp, char * buf, int count)
{int left,chars,nr;struct buffer_head * bh;if ((left=count)<=0)return 0;while (left) {if (nr = bmap(inode,(filp->f_pos)/BLOCK_SIZE)) {if (!(bh=bread(inode->i_dev,nr)))//4.2.2break;} elsebh = NULL;nr = filp->f_pos % BLOCK_SIZE;chars = MIN( BLOCK_SIZE-nr , left );filp->f_pos += chars;left -= chars;if (bh) {char * p = nr + bh->b_data;while (chars-->0)put_fs_byte(*(p++),buf++);brelse(bh);} else {while (chars-->0)put_fs_byte(0,buf++);}}inode->i_atime = CURRENT_TIME;return (count-left)?(count-left):-ERROR;
}
4.2.2 bread

根据设备号,数据块号,创建一个buffer_head

/** bread() reads a specified block and returns the buffer that contains* it. It returns NULL if the block was unreadable.*/
struct buffer_head * bread(int dev,int block)//从dev,block获得buffer_head,一般用这个就可以
{struct buffer_head * bh;if (!(bh=getblk(dev,block)))//拿一块空闲的buffer_headpanic("bread: getblk returned NULL\n");if (bh->b_uptodate)return bh;ll_rw_block(READ,bh);//将硬件的数据读取到buffer_headwait_on_buffer(bh);if (bh->b_uptodate)return bh;brelse(bh);//释放锁return NULL;
}

4.3 写文件

4.3.1 file_write

根据(filp->f_pos)/BLOCK_SIZE计算对应的块号block,如果文件不够大,需要扩容。
根据inode->i_dev和block调用bread获得buffer_head
将用户空间的buf拷贝到buffer_head中b_data。

int file_write(struct m_inode * inode, struct file * filp, char * buf, int count)
{off_t pos;int block,c;struct buffer_head * bh;char * p;int i=0;/** ok, append may not work when many processes are writing at the same time* but so what. That way leads to madness anyway.*/if (filp->f_flags & O_APPEND)pos = inode->i_size;elsepos = filp->f_pos;while (i<count) {if (!(block = create_block(inode,pos/BLOCK_SIZE)))//如果写文件的时候发现文件不够大,就要扩容break;if (!(bh=bread(inode->i_dev,block)))break;c = pos % BLOCK_SIZE;p = c + bh->b_data;bh->b_dirt = 1;c = BLOCK_SIZE-c;if (c > count-i) c = count-i;pos += c;if (pos > inode->i_size) {inode->i_size = pos;inode->i_dirt = 1;}i += c;while (c-->0)*(p++) = get_fs_byte(buf++);brelse(bh);}inode->i_mtime = CURRENT_TIME;if (!(filp->f_flags & O_APPEND)) {filp->f_pos = pos;inode->i_ctime = CURRENT_TIME;}return (i?i:-1);
}
4.3.2 sys_sync

ll_rw_block将buffer_head的脏数据同步到块设备

int sys_sync(void)//系统调用,同步块设备和内存高速缓存中数据
{int i;struct buffer_head * bh;sync_inodes();      /* write out inodes into buffers *///将修改的inode数据写入到buffer_head.bh = start_buffer;for (i=0 ; i<NR_BUFFERS ; i++,bh++) {wait_on_buffer(bh);if (bh->b_dirt)ll_rw_block(WRITE,bh);//真的写到块设备中}return 0;
}

五、块设备

5.1 内部结构


struct d_inode {  //块设备中对应的inode的结构体unsigned short i_mode;unsigned short i_uid;unsigned long i_size;unsigned long i_time;unsigned char i_gid;unsigned char i_nlinks;unsigned short i_zone[9];
};
struct dir_entry { //目录unsigned short inode;//inodechar name[NAME_LEN];//inode对应的文件名
};


5.2 mount("dev/block/sda0","/sdcard")

第一步:从dev/block/sda0中读取第0块inode块上的数据,读取第一个d_inode的数据,并构建内存中的m_inode。

第二步:将m_inode的i_num和"sdcard"按照dir_entry的结构体存放在"/"目录对应m_inode指向的i_zone数据区域


5.3 int fd = open("/sdcard/1.txt")

第一步:找到m_inode("sdcard")

读取"/"对应的m_inode("/")的 i_zone[9]的数据到内存中
根据"sdcard"得到inode号,拿到"sdcard"对应的m_inode("sdcard"),m_inode已经在mount中创建。

第二步:创建m_inode("1.txt")

读取m_inode("sdcard")的 i_zone[9]的数据到内存中
根据"1.txt",拿到1.txt文件对应的d_inode的号
计算d_inode号找到对应的d_inode结构体存放在块设备的块号,块号=inode/一个块最多存放的d_inode结构体
读取的块号对应数据到内存中,读取对应的d_inode数据,构建m_inode("sdcard")

第三步:将fd指向file指向m_inode

创建file指向m_inode("1.txt")
file[fd]指向file
返回fd


从此形成fd->file->m_inode的对应关系,write read close 都可以对应的转化成file的操作,对应的m_inode的操作

六、目前Linux的架构

构建了一个VFS层,虚拟文件系统,各类文件系统可以更好的兼容,EXT4,F2FS
文件系统和块设备的数据交互,用BIO代替了buffer_head
新增了Block Layer层对BIO进行合并调度

  回复「 篮球的大肚子」进入技术群聊

回复「1024」获取1000G学习资料

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467861.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

地铁客流检测训练问题记录

一 报错&#xff1a; File "/home/jz/py-faster-rcnn/tools/../lib/datasets/pascal_voc.py", line 183, in _load_pascal_annotation tree ET.parse(filename) File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1182, in parse tree.parse(sou…

记一次和摄像头的摩擦经历

因为时间的原因&#xff0c;这次点亮摄像头的时间特别短&#xff0c;昨天下午模组到公司&#xff0c;今天下午点亮。几个人一起调试&#xff0c;发现的问题也很多&#xff0c;今天下午发现有一个怀疑的问题&#xff0c;我马上驱车几十公里去模组厂调试&#xff0c;回来的时候&a…

声明为指针,定义为数组,声明为数组,定义为指针

之前写发的那篇指针和数组的文章&#xff0c;有网友评论觉得不是很舒服&#xff0c;我自己看了&#xff0c;觉得很不满意。所以想再写写&#xff0c;尽量把这个东西讲清楚。#定义为数组&#xff0c;声明为指针1.c中#include "stdio.h" char array[] "abcd"…

f2fs存储结构初探

前言学习文件系统的第一步&#xff0c;先搞清楚文件系统在设备上的存储结构&#xff0c;先来简单了解一下。F2FS空间布局图和描述选自《F2FS技术拆解》https://mp.weixin.qq.com/s/k1ibtWF_TRQi8wbqUGjMrgF2FS空间布局F2FS空间布局整个存储空间被划分为6个区域&#xff1a;超级…

net.conn read 判断数据读取完毕_高并发:缓存模式以及缓存的数据一致性

缓存由于其高性能&#xff0c;支持高并发的特性&#xff0c;在高并发的项目中不可或缺。被大家广泛使用的有Redis&#xff0c;Memcached等。本文主要探讨几种常见的缓存的读写模式&#xff0c;以及如何来保证缓存和数据库的数据一致性。这里大家可以关注一下我的个人专栏《PHP进…

PHP配置环境中开启GD库

下配置好的PHP环境中&#xff0c;GD库不像windows那样可以直接用&#xff0c;而是默认关闭&#xff0c;需要把它打开&#xff0c;去到php.ini文件中 找到php_gd2.dll把分号去掉即可。(注&#xff1a;GD库跟绘制二维码等有关) 转载于:https://www.cnblogs.com/mrszhou/p/7421161…

USB协议普及文

#目录#USB#USB On-The-Go Supplement#技术指标#机械和电气标准#编码方式#软件架构#端点#HCD#USB 封包格式#设备分类#USB接头#电源#Storage#人机接口设备&#xff08;HID&#xff09;通用串行总线(Universal Serial Bus, USB&#xff09;是连接外部设备的一个串口总线标准&#…

mysql jdbc驱动_JDBC认识与实践

一、什么是JDBC&#xff1f;Java数据库连接&#xff0c;&#xff08;Java Database Connectivity&#xff0c;简称JDBC&#xff09;是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口&#xff0c;提供了诸如查询和更新数据库中数据的方法。JDBC思维导图二、JDBC应…

画布实现拼图原理

1. 页面布局 1.1 bg: 背景提示图&#xff0c;使用半透明效果&#xff0c;移动图片后显示&#xff0c;层级最低&#xff0c;z-index:-1; 1.2 cvs: 当前画布&#xff0c;层级默认0&#xff1b; 1.3 content: 覆盖在画布之上 z-index:1; 1.3.1 currentCheckpoint: 显示当前关卡数…

晚归的码农老公

呃&#xff0c;我是本号主的贤内助&#xff0c;我们的儿子叫楠哥~由于疫情的影响&#xff0c;我们已经很久没有带楠楠回他外婆家玩了&#xff0c;只能用电话跟外婆聊聊家常。每一次通话结束&#xff0c;他外婆总会加问一句&#xff0c;启发回来了吧。我们每次的回答都是&#x…

Rockchip USB转485

#RS232 / RS485 简介#RS232#RS485#R485与RS232比较#开发#DTS配置#驱动开发#POSIX规范API#HAL层以上APP层#APK call JNI#APP#调试#log开启#RS232/RS485/RS422常见问题#RS232 / RS485 简介#RS232RS-232是美国电子工业联盟&#xff08;EIA&#xff09;制定的串行数据通信的接口标…

高嘌呤食物搜索引擎_“高嘌呤”的食物已发现,尿酸高的人,要尽量挑着吃!...

导语&#xff1a;在我们的生活中又出现了一种现象&#xff0c;越来越频发&#xff0c;也是越来越倾向于年轻化&#xff0c;那么就是高尿酸&#xff0c;现在高尿酸已经被列为了我们国家的第四高&#xff0c;身体内的嘌呤含量是一部分&#xff0c;那么另一部分也是来源于我们平时…

罗老师带货了

相对比其他事情&#xff0c;我对罗老师带货很感兴趣&#xff0c;也许这样说&#xff0c;我对罗老师这个人比较感兴趣。罗老师是一个经过大风大浪的男人&#xff0c;经过了各种风雨&#xff0c;终于找到自己的位置&#xff0c;也决定在这个位置上发光发热了。我觉得他会像冯提莫…

【Linux笔记】LED驱动程序

前言 上一篇我们分享了字符设备驱动框架&#xff1a;【Linux笔记】驱动基础篇&#xff0c;当时分享的是hello驱动程序。学STM32我们从点灯开始&#xff0c;学Linux驱动我们自然也要点个灯来玩玩&#xff0c;尽量在从这些基础例程中榨取知识&#xff0c;细抠、细抠&#xff0c;为…

oracle trigger 延迟执行_一文详解Spring任务执行和调度

一、概述Spring框架分别使用TaskExecutor和TaskScheduler接口提供异步执行和任务调度的抽象。Spring还提供了这些接口的实现&#xff0c;这些接口支持线程池或将其委托给应用服务器环境中的CommonJ。二、TaskExecutorSpring 2.0 开始引入的新的抽像。Executors 是线程池的Java …

数据库(2)

文章目录数据表操作完整性约束AUTO_INCREMWNTNOT NULL非空唯一性约束UNIQUE KEY&#xff08;KEY可省略&#xff09;修改表结构的方式修改表名添加字段删除字段修改字段添加默认值删除默认值添加主键删除主键添加唯一删除唯一修改表的存储引擎设置自增长的值删除数据表DML插入数…

中resource文件夹的作用_冲突与碰撞:OpenStack中的虚拟机和裸机

冲突与碰撞&#xff1a;OpenStack中的虚拟机和裸机要虚拟化还是非虚拟化&#xff1f;如果您追求性能&#xff0c;那么就没有争议——裸机仍然胜过虚拟机&#xff1b;特别是对于I/O密集型应用程序。但是&#xff0c;除非您可以保证充分利用它&#xff0c;否则是有代价的。在本文…

看看大神是如何计算32位数中‘1’的个数

偶然看到一份代码&#xff0c;代码是计算一个int数中 ‘1’ 的个数的&#xff0c;当然&#xff0c;可能这不是什么值得讨论的事情&#xff0c;但如果你看看大神如何写这段代码的&#xff0c;就觉得这个是一个需要了解的知识。int count_bits(int x) {register int xxx;xxxx-((…

MySQL运算符,函数,索引,图形化管理工具

文章目录运算符算术运算符比较运算符逻辑运算符MySQL运算符数字函数字符串函数日期时间函数条件函数系统信息函数加密函数其他常用函数MySQL索引索引的概念索引的分类创建索引创建表时创建索引在已经存在的表上创建索引删除索引MySQL图形化管理工具PHPMyAdminSQLyog运算符 算术…

Linux下的gpio,gpiod

GPIO 应该是每个嵌入式设备都避免不了的。最近在做项目的时候&#xff0c;也遇到这方面的问题&#xff0c;所以简单总结一下现在内核里面多了gpiod的来控制gpio口&#xff0c;相对于原来的形式&#xff0c;使用gpiod的好处是我们申请后不进行free也没有什么问题。但是你要是使用…