在应用程序中实现对NandFlash的操作

以TC58NVG2S3ETA00 为例:

下面是它的一些物理参数:

图一

image

 

 

 

 

 

 

图二

image

 

 

 

 

 

 

 

 

 

 

 

图三

image

 

 

 

 

 

 

 

 

图四

image

 

 

 

 

 

 

 

 

 

 

 

 

 

图五

image

 

 

 

图6-0

image

图6-1

image

 

说明一下,在图6-1中中间的那个布局表可以看做是实际的NandFlash一页数据的布局,其中Data区域用于存放有效的数据,也就是我们可以通过类似read、write、pread、pwrite可以访问的区域,那每页中的64字节的OOB区域是无法通过前面的几个函数访问的,他们会自动跳过OOB区域,访问OOB区域需要借助特殊的命令。

简单说明一下:Data A(512B)对应的ECC校验码存放在ECC for Data A(4 byte)中,OOB A (8byte) 对应的ECC校验码存放在紧接着的下一个ECC for Data A(4 byte)中,虽然用4字节存放ECC,但是对于本例,ECC只占3个字节。在实际使用中如果解决方案中用不到OOB A/B/C/D,可以不用管他们对应的ECC,只需要关心Data区域对应的ECC。如果使能了硬件ECC,硬件会自动把计算生成的ECC写到OOB中。可以参考http://www.cnblogs.com/pengdonglin137/p/3467960.html 。

读NandFlash需要按页读,即一次读一页;写NandFlash需要按页写,即每次写一页;擦除NandFlash需要按块擦,即每次要擦除一块。

对与NandFlash等块设备的访问操作,mtd-utils工具集中提供了非常好的支持(可以到http://www.linux-mtd.infradead.org/进行了解),要使用mtd-utils工具集首先需要搞到mtd-utils的源码,并且使用目标设备上的交叉工具编译链进行编译,具体方法可以参考:http://www.cnblogs.com/pengdonglin137/p/3415550.html,其中介绍了如何生成可以再目标板上运行的mtd-utils工具。关于mtd-utils工具的使用可以参考:http://www.cnblogs.com/pengdonglin137/p/3415663.html 其中介绍了mtd-utils中常用的工具。

我们可以参考mtd-utils中工具的实现,从而完成在自己的应用程序中实现对NandFlash的操作。常用的命令如下:

#define MEMGETINFO        _IOR('M', 1, struct mtd_info_user)
#define MEMERASE        _IOW('M', 2, struct erase_info_user)
#define MEMWRITEOOB        _IOWR('M', 3, struct mtd_oob_buf)
#define MEMREADOOB        _IOWR('M', 4, struct mtd_oob_buf)
#define MEMLOCK            _IOW('M', 5, struct erase_info_user)
#define MEMUNLOCK        _IOW('M', 6, struct erase_info_user)
#define MEMGETREGIONCOUNT    _IOR('M', 7, int)
#define MEMGETREGIONINFO    _IOWR('M', 8, struct region_info_user)
#define MEMSETOOBSEL        _IOW('M', 9, struct nand_oobinfo)
#define MEMGETOOBSEL        _IOR('M', 10, struct nand_oobinfo)
#define MEMGETBADBLOCK        _IOW('M', 11, __kernel_loff_t)
#define MEMSETBADBLOCK        _IOW('M', 12, __kernel_loff_t)
#define OTPSELECT        _IOR('M', 13, int)
#define OTPGETREGIONCOUNT    _IOW('M', 14, int)
#define OTPGETREGIONINFO    _IOW('M', 15, struct otp_info)
#define OTPLOCK            _IOR('M', 16, struct otp_info)
#define ECCGETLAYOUT        _IOR('M', 17, struct nand_ecclayout_user)
#define ECCGETSTATS        _IOR('M', 18, struct mtd_ecc_stats)
#define MTDFILEMODE        _IO('M', 19)
#define MEMERASE64        _IOW('M', 20, struct erase_info_user64)
#define MEMWRITEOOB64        _IOWR('M', 21, struct mtd_oob_buf64)
#define MEMREADOOB64        _IOWR('M', 22, struct mtd_oob_buf64)
#define MEMISLOCKED        _IOR('M', 23, struct erase_info_user)

 

打开设备

这里需要注意的是,打开的设备结点是/dev/mtd?,而不是/dec/mtdblock?,原因可以参考:

http://www.cnblogs.com/pengdonglin137/p/3316523.html,其中介绍了mtd与mtdblock的区别。

fd = open ("/dev/mtd0", O_SYNC | O_RDWR);

 

获取设备信息

#include <linux/types.h>
structmtd_info_user { __u8 type; __u32 flags; __u32 size; // Total size of the MTD
    __u32 erasesize;__u32 writesize;__u32 oobsize;// Amount of OOB data per block (e.g. 16)/*
The below two fields are obsolete and broken, do not use them * (TODO: remove at some point) */
__u32 ecctype;__u32 eccsize;
};struct mtd_info_user mtd;
ioctl(fd, MEMGETINFO,&mtd) ;
 其中type可以用来区分是NorFlash还是NandFlash。

擦除NandFlash

#include <mtd/mtd-abi.h>
#include <linux/types.h>struct erase_info_user {__u32 start;__u32 length;
};typedef struct erase_info_user erase_info_t;erase_info_t erase;int isNAND, bbtest = 1;erase.length = DevInfo->erasesize;
// erase.length 表示的是擦除大小,也就是一块的大小,如128KB
// DevInfo->size 为某个/dev/mtdx的大小
// erasse.start应该是按块对齐递增

isNAND = (DevInfo->typenum== MTD_NANDFLASH) ? 1 : 0;for (erase.start = 0; erase.start <  DevInfo->size; erase.start += DevInfo->erasesize) 
{if (bbtest) {loff_t offset = erase.start;int ret = ioctl(DevInfo->fd, MEMGETBADBLOCK, &offset); //判断是不是坏块if (ret > 0) {if (!quiet)DEBUG ("\nSkipping bad block at 0x%08x\n", erase.start);continue;//发现是坏块,应该跳过
            } else if (ret < 0) {if (errno == EOPNOTSUPP) {bbtest = 0;if (isNAND) {fprintf(stderr, "%s: Bad block check not available\n", DevInfo->dir);return 1;}} else {fprintf(stderr, "\n%s: MTD get bad block failed: %s\n", DevInfo->dir, strerror(errno));return 1;}}}if (!quiet){fprintf(stderr, "\rErasing %d Kibyte @ %x -- %2llu %% complete.", \(DevInfo->erasesize) / 1024, erase.start,(unsigned long long) erase.start * 100 / (DevInfo->size));}if (ioctl(DevInfo->fd, MEMERASE, &erase) != 0) //执行擦除操作
        {fprintf(stderr, "\n%s: MTD Erase failure: %s\n", DevInfo->dir,strerror(errno));continue;}
}

写NandFlash

这里分为写数据区和写OOB区

写数据区,对于本例一次要写一页,也就是2KB,写OOB区,对于本例可以操作的只有32字节,剩下的32字节用于存放ECC。

struct mtd_oob_buf {__u32 start;__u32 length;unsigned char *ptr;
};int nandwrite(DeviceInfo* meminfo)
{int imglen = 0, pagelen;bool baderaseblock = false;int blockstart = -1;loff_t offs;int ret, readlen;unsigned char tmp_oob[32];//OOB A/B/C/D,一共32字节struct mtd_oob_buf OOB_INFO ;sourceaddr = meminfo->head->file_offset;   //要读的部分在镜像文件中的偏移量sourcelen = meminfo->head->size;           //要读的部分的大小int num_to_read = 0;OOB_INFO.start = 0;OOB_INFO.length = meminfo->head->oob_usr_length;   //32字节,用户可以访问的OOB的大小,也就是OOB A/B/C/DOOB_INFO.ptr = tmp_oob;pagelen = meminfo->writesize; // 2KBimglen = sourcelen;           // 镜像文件的长度
mtdoffset = meminfo->head->flash_offset;  //要写的部分在/dev/mtdx中的偏移量,以字节为单位/* Determine if we are reading from standard input or from a file. */if (0 == sourceaddr) {DEBUG("Have no sourceaddr return ****************************\n");return 1;}// Check, if length fits into deviceif ( ((imglen / pagelen) * meminfo->writesize) > (meminfo->size - mtdoffset)) {fprintf (stderr, "Image %d bytes, NAND page %d bytes, OOB area %u bytes, device size %u bytes\n",imglen, pagelen, meminfo->writesize, meminfo->size);perror ("Input file does not fit into device");goto closeall;}while ((imglen>0) && (mtdoffset < meminfo->size)){//blockstart 将来存放的是正在写的那块的起始地址,并且是块对齐的//mtdoffset 表示的是在某个mtd设备中的整体偏移量,可以按块递增,也可以按页递增//设置blockstart的目的是:// 假如检测到一个好的块,开始进行写操作,但是在写的过程中发生了写错误,可以认为这块已经//是坏块了,需要重新再找一个好的块,然后重新写入之前的数据,因此需要知道刚才那个坏块的起始地址// mtdoffset & (~meminfo->erasesize + 1) 这种获取块起始地址的算法值得借鉴while (blockstart != (mtdoffset & (~meminfo->erasesize + 1))){blockstart = mtdoffset & (~meminfo->erasesize + 1);offs = blockstart;baderaseblock = false;if (!quiet){fprintf (stderr, "\rWriting data to block %d at offset 0x%x", \blockstart / meminfo->erasesize, blockstart);}/* Check all the blocks in an erase block for bad blocks */// meminfo->fd 是某个/dev/mtdx的文件描述符do {if ((ret = ioctl(meminfo->fd, MEMGETBADBLOCK, &offs)) < 0){perror("ioctl(MEMGETBADBLOCK)");goto closeall;}if (ret == 1){baderaseblock = true;if (!quiet){fprintf (stderr, "Bad block at %x block(s) ""from %x will be skipped\n",(int) offs, blockstart);}}if (baderaseblock){mtdoffset = blockstart + meminfo->erasesize;}offs +=  meminfo->erasesize;} while ( offs < blockstart + meminfo->erasesize );}readlen = meminfo->writesize;  // 2KBif (0 != sourceaddr){if((meminfo->head->imageType == YAFFS) || (meminfo->head->imageType == OOB_RAW)){writeoob = true;}else{writeoob = false;}memset(writebuf, 0xff, sizeof(writebuf));if(imglen < readlen){num_to_read = imglen;}else{num_to_read = readlen;}// 从镜像文件中偏移量为sourceaddr处读取num_to_read个字节到writebuf中// ALLIMAGEFD 为镜像文件的文件描述符if(pread(ALLIMAGEFD, writebuf, num_to_read, sourceaddr) < 0) {perror("fail to pread\n");return -1;}sourceaddr += num_to_read;if(writeoob){memset(tmp_oob, 0xff , OOB_FREE_MAX);// 从镜像文件中偏移量为sourceaddr+meminfo->head->oob_usr_offset处读取meminfo->head->oob_usr_length个字节到tmp_oob中,其中meminfo->head->oob_usr_offset是OOB A相对与OOB区域的偏移量,meminfo->head->oob_usr_length 在本例中为32字节if(pread(ALLIMAGEFD, tmp_oob, meminfo->head->oob_usr_length, sourceaddr+meminfo->head->oob_usr_offset) < 0){perror("fail to pread\n");return -1;}sourceaddr += meminfo->oobsize;}}if(-1 == pwrite(meminfo->fd, writebuf, meminfo->writesize, mtdoffset)) //写NandFlash
    {/*下面这段程序所完成的就是刚才所说的在写之前检测到是好块,但是在写的过程出现了写错误,这个时候需要完成?如下流程:1、计算已经在当前块上写入多少内容,比如下面的rewind_blocks是为了计算在当前块上已经写了多少页,这里需要注意的是;rewind_bytes又加了一个readlen,也就是一页的大小,目的是保证sourceaddr的可以回退到刚开始写当前块是sourceaddr的值,可以看到在上面的程序中每次将要写的内容读到writebuf后,sourceaddr已经进行了自增操作,并没有保证刚读到writebuf中的内容可以成功写入。但是mtdoffset进行自增的前提是偏移量为mtdoffset的页写成功。其实程序可以这么改进:将sourceaddr的自增操作跟mtdoffset的自增操作放在一起,此时rewind_bytes就不需要再加readlen了。对于oob,一般只有yaffs镜像中有oob,而向cramfs、jffs2、ubifs这没有,如果有oob也需要对rewind_byte进行处理2、对当前块进行擦除3、如果需要进行坏块标记,则将当前块标记为坏块4、将mtdoffset指向当前块的下一块起始地址5、恢复imglen为刚开始处理当前块时的值,由于imglen也是保证当前页成功写入后才自减,所以只需要加上rewind_blocks即可*/int rewind_blocks;off_t rewind_bytes;erase_info_t erase;perror("ioctl(MEMEWRITEPAGE)");/* Must rewind to blockstart if we can */rewind_blocks = (mtdoffset - blockstart) / meminfo->writesize; /* Not including the one we just attempted */rewind_bytes = (rewind_blocks * meminfo->writesize) + readlen;if (writeoob){rewind_bytes += (rewind_blocks + 1) * meminfo->oobsize;}sourceaddr -= rewind_bytes;erase.start = blockstart;erase.length = meminfo->erasesize;fprintf(stderr, "Erasing failed write from %08lx-%08lx\n",(long)erase.start, (long)erase.start+erase.length-1);if (ioctl(meminfo->fd, MEMERASE, &erase) != 0){perror("MEMERASE");goto closeall;}if (markbad){loff_t bad_addr = mtdoffset & (~meminfo->erasesize + 1);fprintf(stderr, "Marking block at %08lx bad\n", (long)bad_addr);if (ioctl(meminfo->fd, MEMSETBADBLOCK, &bad_addr)) {perror("MEMSETBADBLOCK");/* But continue anyway */}}mtdoffset = blockstart + meminfo->erasesize;imglen += rewind_blocks * meminfo->writesize;if(writeoob){imglen += rewind_blocks * meminfo->oobsize;}continue;}imglen -= readlen;if(writeoob){imglen -= meminfo->oobsize;OOB_INFO.start = mtdoffset;if (ioctl(meminfo->fd, MEMWRITEOOB, &OOB_INFO)){perror("fail to ioctl");}}mtdoffset += meminfo->writesize;}closeall:if ((imglen > 0)){perror ("Data was only partially written due to error\n");exit (EXIT_FAILURE);}return EXIT_SUCCESS;
}

 

对于写NandFlash,有的设备支持一次性把data和oob一块写进去。代码如下:

struct mtd_info_user {uint8_t type;uint32_t flags;uint32_t size;     // Total size of the MTD
    uint32_t erasesize;uint32_t writesize;uint32_t oobsize;   // Amount of OOB data per block (e.g. 16)/* The below two fields are obsolete and broken, do not use them* (TODO: remove at some point) */uint32_t ecctype;uint32_t eccsize;
};struct mtd_epage_buf 
{unsigned long long  start;unsigned long       data_len;unsigned long       oob_len;unsigned char     * data_ptr;unsigned char     * oob_ptr; 
};

#define MEMEWRITEPAGE _IOWR('M', 23, struct mtd_epage_buf)

 

#define MAX_PAGE_SIZE 8192
#define MAX_OOB_SIZE 512

 

/*
* Buffer array used for writing data
*/
unsigned char writebuf[MAX_PAGE_SIZE];
char oobbuf[MAX_OOB_SIZE];

int nandwrite(int argc, char * const argv[])
{/*int cnt = 0;*/int fd = -1;/*int ifd = -1;*/int imglen = 0, pagelen;bool baderaseblock = false;int blockstart = -1;struct mtd_info_user meminfo;struct mtd_epage_buf eccbuf;loff_t offs;int ret, readlen;/*process_options(argc, argv);*/mtdoffset = 0; /* reinit */erase_buffer(oobbuf, sizeof(oobbuf));if (pad && writeoob) {fprintf(stderr, "Can't pad when oob data is present.\n");exit (EXIT_FAILURE);}/* Open the device */if ((fd = open(mtd_device, O_RDWR)) == -1) {perror(mtd_device);exit (EXIT_FAILURE);}/* Fill in MTD device capability structure */if (ioctl(fd, MEMGETINFO, &meminfo) != 0) {perror("MEMGETINFO");close(fd);exit (EXIT_FAILURE);}/* Determine if we are reading from standard input or from a file. */if (NULL == sourceaddr) {DEBUG("Have no sourceaddr return ****************************\n");return 0;}pagelen = meminfo.writesize + ((writeoob) ? meminfo.oobsize : 0);/** For the standard input case, the input size is merely an* invariant placeholder and is set to the write page* size. Otherwise, just use the input file size.** TODO: Add support for the -l,--length=length option (see* previous discussion by Tommi Airikka <tommi.airikka@ericsson.com> at* <http://lists.infradead.org/pipermail/linux-mtd/2008-September/* 022913.html>*/imglen = sourcelen;// Check, if file is page-alignedif ((!pad) && ((imglen % pagelen) != 0)) {fprintf (stderr, "Input file is not page-aligned. Use the padding ""option.\n");goto closeall;}// Check, if length fits into deviceif ( ((imglen / pagelen) * meminfo.writesize) > (meminfo.size - mtdoffset)) {fprintf (stderr, "Image %d bytes, NAND page %d bytes, OOB area %u bytes, device size %u bytes\n",imglen, pagelen, meminfo.writesize, meminfo.size);perror ("Input file does not fit into device");goto closeall;}const int allSizeConst = imglen;/*DEBUG("file:%s, line:%d, imglen:%d, mtdoffset:%d, meminfo.size:%d\n", __FILE__, __LINE__, imglen, mtdoffset, meminfo.size);*//** Get data from input and write to the device while there is* still input to read and we are still within the device* bounds. Note that in the case of standard input, the input* length is simply a quasi-boolean flag whose values are page* length or zero.*/while (imglen && (mtdoffset < meminfo.size)) {// new eraseblock , check for bad block(s)// Stay in the loop to be sure if the mtdoffset changes because// of a bad block, that the next block that will be written to// is also checked. Thus avoiding errors if the block(s) after the// skipped block(s) is also bad (number of blocks depending on// the blockalignwhile (blockstart != (mtdoffset & (~meminfo.erasesize + 1))) {blockstart = mtdoffset & (~meminfo.erasesize + 1);offs = blockstart;baderaseblock = false;if (!quiet)fprintf (stdout, "Writing data to block %d at offset 0x%x\n",blockstart / meminfo.erasesize, blockstart);/* Check all the blocks in an erase block for bad blocks */do {if ((ret = ioctl(fd, MEMGETBADBLOCK, &offs)) < 0) {perror("ioctl(MEMGETBADBLOCK)");goto closeall;}if (ret == 1) {baderaseblock = true;if (!quiet)fprintf (stderr, "Bad block at %x block(s) ""from %x will be skipped\n",(int) offs, blockstart);}if (baderaseblock) {mtdoffset = blockstart + meminfo.erasesize;}offs +=  meminfo.erasesize;} while ( offs < blockstart + meminfo.erasesize );}readlen = meminfo.writesize;if (NULL != sourceaddr) {if (pad && (imglen < readlen)){readlen = imglen;erase_buffer(writebuf + readlen, meminfo.writesize - readlen);}memcpy(writebuf, sourceaddr, readlen);sourceaddr += readlen;} if (writeoob) {memcpy(oobbuf, sourceaddr, meminfo.oobsize);sourceaddr += meminfo.oobsize;}eccbuf.data_ptr = writebuf;eccbuf.data_len = meminfo.writesize;eccbuf.oob_ptr  = oobbuf;eccbuf.oob_len  = meminfo.oobsize;eccbuf.start    = mtdoffset;if (ioctl(fd, MEMEWRITEPAGE, &eccbuf) != 0){int rewind_blocks;off_t rewind_bytes;erase_info_t erase;perror("ioctl(MEMEWRITEPAGE)");/* Must rewind to blockstart if we can */rewind_blocks = (mtdoffset - blockstart) / meminfo.writesize; /* Not including the one we just attempted */rewind_bytes = (rewind_blocks * meminfo.writesize) + readlen;if (writeoob)rewind_bytes += (rewind_blocks + 1) * meminfo.oobsize;sourceaddr -= rewind_bytes;erase.start = blockstart;erase.length = meminfo.erasesize;fprintf(stderr, "Erasing failed write from %08lx-%08lx\n",(long)erase.start, (long)erase.start+erase.length-1);if (ioctl(fd, MEMERASE, &erase) != 0) {perror("MEMERASE");goto closeall;}if (markbad) {loff_t bad_addr = mtdoffset & (~meminfo.erasesize + 1);fprintf(stderr, "Marking block at %08lx bad\n", (long)bad_addr);if (ioctl(fd, MEMSETBADBLOCK, &bad_addr)) {perror("MEMSETBADBLOCK");/* But continue anyway */}}mtdoffset = blockstart + meminfo.erasesize;imglen += rewind_blocks * meminfo.writesize;continue;}imglen -= (readlen + meminfo.oobsize);mtdoffset += meminfo.writesize;

}closeall:close(fd);if ((imglen > 0)) {perror ("Data was only partially written due to error\n");exit (EXIT_FAILURE);}/* Return happy */return EXIT_SUCCESS; }

 

 

读OOB

读OOB跟写OOB类似,只不过使用的命令是MEMREADOOB。

#include <sys/ioctl.h>
#include <stdio.h>
#include <mtd/mtd-user.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>#define N 32
#define OFS   (0)
#define block_size (128*1024)
#define page_size  (2*1024)int main(int argc, const char *argv[])
{int fd;int i, j;unsigned char oob_data[32] ={0x53, 0x50, 0x4c, 0x20, 0, 0xff, 0, 0xff, 0x53, 0x50, 0x4c, 0x20, 0, 0xff, 0, 0xff, 0x53, 0x50, 0x4c, 0x20, 0, 0xff, 0, 0xff, 0x53, 0x50, 0x4c, 0x20, 0, 0xff, 0, 0xff};unsigned char oobbuf[N];struct mtd_oob_buf oob = {0, N, oobbuf};struct mtd_oob_buf my_oob = {0, N, oob_data};fd = open("/dev/mtd0", O_RDWR);if(fd < 0){perror("fail to open\n");exit(-1);}if(ioctl(fd, MEMWRITEOOB, &my_oob)){perror("fail to ioctl");exit(-1);}memset(oobbuf, 0, sizeof(oobbuf));oob.start = OFS;if (ioctl(fd, MEMREADOOB, &oob)){perror("fail to ioctl");exit(-1);}for(i=0; i<N; i++){if(i%8 == 0){printf("\n");}printf("%#x ", oobbuf[i]);}printf("\n\n");close (fd);return 0;}

 

以上只是本人在工作中遇到的,仅供参考。

转载于:https://www.cnblogs.com/pengdonglin137/p/3468953.html

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

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

相关文章

1 盒用 1 年,这个防雾黑科技,让你的眼镜再也不起雾!

▲ 点击查看本来被疫情这么一闹&#xff0c;口罩就已经成了我们离不开的刚需品。现在天气大幅度降温&#xff0c;口罩更不能摘了&#xff0c;必须得御寒防疫两手抓&#xff01;虽然口罩的优点有目共睹&#xff0c;但它最大的 Bug 也够大伙儿吐槽一顿了——口罩眼镜一秒起雾&…

福利好礼现金大奖等你来→首届 .NET Conf China Hackathon 火热报名中!

.NET 开源以来越来越多的开发者和企业开始加入到 .NET 的生态。.NET 6 是 .NET Framework, .NET Core 和 Mono 三个平台完成融合的第一个长期支持版本&#xff0c;无论你是开发云原生应用&#xff0c;还是 IOT 应用&#xff0c;移动端&#xff0c;桌面应用&#xff0c;.NET 6 …

java属于面相_[Java教程]面相对象

[Java教程]面相对象0 2018-09-13 16:00:26面向对象那什么是面向对象&#xff1f; 在Java 中&#xff0c;我们是一切皆对象&#xff0c;所有的方法都是围绕着对象来的。面相对象是相对面向过程而来的&#xff0c;他们都是一种思想&#xff0c;面向过程&#xff0c;强调的是一种功…

下面由我来给大家表演个绝活

1 娶个老婆真不容易啊&#xff01;▼2 孩子有些东西不是努力就能吃到的啊▼3 给大家表演个绝活▼4 在危险的边缘一点点试探▼5 你知道为什么狗子要拆家了吗▼6 外国版姜太公钓鱼愿者上钩▼7 最好看的那个晚霞永远出现在教室的窗外▼你点的每个赞&#xff0c;我都认真当成…

.Net 下高性能分表分库组件-连ShardingCore接模式原理

ShardingCore 一款ef-core下高性能、轻量级针对分表分库读写分离的解决方案&#xff0c;具有零依赖、零学习成本、零业务代码入侵。Github Source Code 助力dotnet 生态 Gitee Source Code介绍在分表分库领域java有着很多的解决方案,尤其是客户端解决方案(ShardingSphere)&…

php 利用http上传协议(表单提交上传图片 )

主要就是利用php 的 fsocketopen 消息传输。 这里先通过upload.html 文件提交&#xff0c;利用chrome抓包&#xff0c;可以看到几个关键的信息。 首先指定了表单类型为multipart/form-data;。 boundary是分隔符 因为上传文件不在使用原有的http协议了。请求内容不再可能以 x…

重新认识Docker Compose之Sidecar模式

什么是Docker Compose在微服务盛行的今天&#xff0c;我们通常是这么定义Compose的&#xff1a;对容器的统一启动和关闭的编排工具。但是我以前还是有个疑惑&#xff0c;谁会用Compose在一台服务器上部署多个服务呢&#xff1f;干脆直接用单体服务就行了&#xff01;直到我遇到…

数学家看到就把持不住,高斯被它迷得神魂颠倒,2600年的数学史里的一个奇迹……...

全世界只有3.14 % 的人关注了爆炸吧知识数学的美两个字就能说清数学女神很可能是个洁癖她的苛刻就体现在公式里那每一个符号每一个数字都不允许有哪怕一点杂质如此才是她最认可的孩子因为知道了勾股定理古人们才创造了辉煌因为有了经典力学公式人类才能探索星辰大海因为掌握了质…

编程语言也环保?C语言领跑,Python、Perl垫底

文 | Travis出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013&#xff09;毋庸置疑&#xff0c;Python 是世界上最流行的编程语言之一&#xff0c;其被广泛运用于人工智能、数据分析、网络爬虫和 Web 开发等领域。在上个月的 TIOBE 榜单中&#xff0c;Python 一举超过…

我靠“读书笔记”闷声赚3万:那些你看不上的行业,往往最赚钱

全世界只有3.14 % 的人关注了爆炸吧知识你有没有计算过&#xff1a;你的时间&#xff0c;值多少钱&#xff1f;如果你月薪5000&#xff0c;一个月工作20天&#xff0c;每天8小时&#xff0c;那么你1小时的价值就是32元。然而&#xff0c;现在请一个打扫卫生的钟点工&#xff0c…

微信企业号开发之如何建立连接

http://www.07net01.com/2014/09/167991.html 连接将使你的企业号更具价值&#xff0c;你可以使用以下三种方式&#xff0c;连接你的企业号及企业应用&#xff1a; 1、企业应用调用企业号提供的接口&#xff0c;管理或查询企业号后台所管理的资源、或给成员发送消息等&#xff…

完全颠覆你世界观的10个科学实验!从此脑洞大开

全世界只有3.14 % 的人关注了爆炸吧知识这两天超模君逛知乎时&#xff0c;发现有人提问&#xff1a;“如何让孩子学会主动”&#xff1f;孩子学会主动是很重要的&#xff0c;特别是在学习方面&#xff0c;懂得主动学习无论是在小时候还是未来都很有优势。可能很多家长都有类似的…

char varchar java_在数据库中varchar与char的区别

char是固定长度类型varchar是可变的长度类型char和varchar类型相似&#xff0c;但是它们的存储和检索方式不同。在MySQL5.0.3&#xff0c;它们在最大长度和是否保留尾部空格也不相同。char和varchar定义了字符串的最大长度。例如&#xff0c;char(30)可以放30个字符。char列的长…

震撼世界的基建狂魔,中国制造的超级工程到底有多牛?

全世界只有3.14 % 的人关注了数据与算法之美1949年的中华大地上一无所有&#xff0c;而现在一栋栋高楼伫立在这片土地上的时候不禁感叹&#xff0c;到底是什么支撑着让我们从一片荒漠变成如今高楼耸立&#xff1f;今天小编带来的这部纪录片《超级工程》&#xff0c;带你认识被称…

C# WPF图表控件之ChartControl用法指南①

“ 引言部分&#xff0c;总领全篇文章的中心内容。”WPF的DevExpress ChartControl是一种功能强大的可视化工具&#xff0c;可帮助您将数据显示为二维或伪三维条形图、区域、线和许多其他形式。01—将数据绑定到Chart SeriesStep 1. 创建新项目并添加图表创建一个新的WPF应用程…

Android之SwipeRefreshLayout

wipeRefreshLayout字面意思就是下拉刷新的布局,继承自ViewGroup,在support v4兼容包下,但必须把你的support library的版本升级到19.1。 提到下拉刷新大家一定对ActionBarPullToRefresh比较熟悉,而如今google推出了更官方的下拉刷新组件,这无疑是对开发者来说比较好的消…

你以后会不会有小三?

1 千万别碰到小孩就去逗他&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 像极了早上刚睡醒炸毛的你▼3 乞丐和社畜的区别▼4 当你学校装了能上网的平板后&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼5 看图写字&#xff08;素材来源网络&#xff0…

CSharp 如何OCR离线识别文本

WPF开发者QQ群&#xff1a; 340500857 | 微信群 -> 进入公众号主页 加入组织由于微信群人数太多入群请添加小编微信号&#xff08;yanjinhuawechat&#xff09;或&#xff08;W_Feng_aiQ&#xff09;邀请入群&#xff08;需备注WPF开发者&#xff09;本项目是基于开源项目P…

第一个h5页面

不废话&#xff0c;先爆照&#xff0c;不好勿喷 我用的软件是WebStorm开发&#xff0c;然后也知道了h5我们可以用谷歌浏览器点击右键&#xff0c;然后点击审查&#xff0c;就可以按照手机屏幕来看到页面 下面是项目的图片 这个是我的index.html文件 <!doctype html> <…

Ubuntu系统备份和恢复

为什么80%的码农都做不了架构师&#xff1f;>>> 安装好Ubuntu之后&#xff0c;别忘了安装 for linux 防火墙和杀毒软件。在备份系统前&#xff0c;请保证系统是无错和干净的&#xff1a;本人操作系统是ubuntu14.04&#xff0c;不知道是系统出了问题还是装的软件有问…