当前位置:网站首页>[MIT 6.S081] Lab 10: mmap
[MIT 6.S081] Lab 10: mmap
2022-07-27 18:27:00 【PeakCrosser】
Lab 10: mmap
- Lab Guidance: https://pdos.csail.mit.edu/6.828/2020/labs/mmap.html
- Lab Code: https://github.com/peakcrosser7/xv6-labs-2020/tree/mmap
mmap (hard)
The main points of
- The implementation only considers memory mapped files
mmapandmunmapsystem call . mmapParametersaddrAlways for 0, The virtual address of the mapping file is determined by the kernel .mmapParametersflagsOnly considerMAP_SHAREDandMAP_PRIVATE.mmapUse lazy allocation for memory mapping .- Define a VMA Structure to describe the information of a piece of virtual memory . You can record with a fixed length array .
step
1. Makefile Modify and system call definitions
- stay
MakefileAdd$U/_mmaptest
- Add something about
mmapandmunmapDefinition declaration of system call . Includekernel/syscall.h,kernel/syscall.c,user/usys.planduser/user.h.




2. Definition vm_area Structure and its array
- stay
kernel/proc.hIn the definition ofstruct vm_areaStructure .vm_areanamely Virtual Memory Area, VMA, Used here to indicate the use ofmmapThe location of the virtual memory area mapped by the system call file 、 size 、 Authority, etc ( In fact, Linux Will be used to represent the relevant information of the entire virtual memory area , Including heaps 、 Stack, etc. , In this part of the experiment, for simple consideration, it is only used to represent the memory of the file mapping part ).struct vm_areaMemory information andmmapThe parameters of the system call basically correspond to , Include the starting address of the mapping 、 Mapped memory length ( size )、 jurisdiction 、mmapSign a 、 File offset and pointer to the file structure .
// Virtual Memory Area - lab10
struct vm_area {
uint64 addr; // mmap address
int len; // mmap memory length
int prot; // permission
int flags; // the mmap flags
int offset; // the file offset
struct file* f; // pointer to the mapped file
};
- stay
struct procDefine relevant fields in the structure .
In simple , According to the tips of the experimental guidance , Use one for each process VMA Array to record mapped memory ( Actually according to Linux Related implementation of , The effect of using linked list will be better ).
Defined hereNVMAExpress VMA Size of array . And instruct procThe structure definesvmaArray , And easy to know VMA Is the private field of the process , So for xv6 Process single user threaded system , visit VMA There is no need to lock .
#define NVMA 16 // the number of VMA in a process - lab10
// Per-process state
struct proc {
// ...
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
struct vm_area vma[NVMA]; // VMA array - lab10
};
3. To write mmap system call
stay kernel/sysfile.c Realize system call in sys_mmap().
- The first is the extraction of parameters , here
mmapThe parameters of Linux The contents in the manual are basically the same , because xv6 There are only integers for the numeric parameters of the system call inintType extraction , thereforelen,offsetParameters are defined here asinttype . - Next is a simple check of parameters . Because in the experiment, only
mmapSystem call file mapping section , thereforeprotandflagsThere are few parameters .flagsParameter must beMAP_SHAREDandMAP_PRIVATEA choice . And forMAP_SHAREDBecause it will be written to the file , Therefore, if the mapped file is not writable and usedPROT_WRITEIf you write the mapping permission, an error will be reported , because You cannot write to a non writable file . At the end of the daylenandoffsetThe nonnegativity of , as well as requirementoffsetyesPGSISZEInteger multiple ( reference Linux Limitations in the manual , In this experimentoffsetIn fact, it's all 0). - Then from the current process VMA Assign a VMA structure , The allocation rules are also relatively simple , According to VMA The address recorded in , if 0 It means that VMA It can be used for this mapping without being used .
- Next is this
mmapThe parameters of are recorded to the assigned VMA in , The first thing to consider is the address used for mapping . According to the experimental guidance , It can be assumedaddrThe parameters are 0, That is, the kernel needs to choose the mapped address by itself .
Here, , The author refers to Linux Memory layout , staykernel/memlayout.hIt definesMMAPMINADDRMacro definition , Used to representmmapThe lowest address that can be used for mapping . If this process has multiple file mappings , Therefore, when actually determining the mapped address , I'll go through it first VMA Array , Find the highest address of the mapped memory , Then take the address up and align the page , Then determine thismmapMapping address of . Of course , If the mapped address space will coverTRAPFRAMEMay be an error . ( This is actually a relatively simple implementation , The problem that mapped memory may be released first from low addresses is not considered , But this implementation is more complex , It is necessary to calculate whether the unallocated space meets this allocation , Implement an algorithm similar to memory partitioning . In addition, there is no check that the heap space may cover the mapped memory , actually xv6 It may not cover the heap spaceTRAPFRAMEInspection , Maybe it's because we won't allocate so much memory , This may cause physical memory exhaustion .)
According to the requirements of the experiment ,mmapThe mapping memory of is also Lazy allocation, So here you only need to VMA The address mapped in the record is the length , And the subsequent passage is in trap Chinese vs page fault Process the actual memory page allocation .
// the minimum address the mmap can ues - lab10
#define MMAPMINADDR (TRAPFRAME - 10 * PGSIZE)
- Other parameters can be directly recorded in the assigned VMA In structure . For referenced file pointers , Need to use
filedup()Increase the number of references , Avoid releasing its file structure . - Finally, return the assigned address . Any failure in the process returns
0xffffffffffffffffnamely-1.
// lab10
uint64 sys_mmap(void) {
uint64 addr;
int len, prot, flags, offset;
struct file *f;
struct vm_area *vma = 0;
struct proc *p = myproc();
int i;
if (argaddr(0, &addr) < 0 || argint(1, &len) < 0
|| argint(2, &prot) < 0 || argint(3, &flags) < 0
|| argfd(4, 0, &f) < 0 || argint(5, &offset) < 0) {
return -1;
}
if (flags != MAP_SHARED && flags != MAP_PRIVATE) {
return -1;
}
// the file must be written when flag is MAP_SHARED
if (flags == MAP_SHARED && f->writable == 0 && (prot & PROT_WRITE)) {
return -1;
}
// offset must be a multiple of the page size
if (len < 0 || offset < 0 || offset % PGSIZE) {
return -1;
}
// allocate a VMA for the mapped memory
for (i = 0; i < NVMA; ++i) {
if (!p->vma[i].addr) {
vma = &p->vma[i];
break;
}
}
if (!vma) {
return -1;
}
// assume that addr will always be 0, the kernel
//choose the page-aligned address at which to create
//the mapping
addr = MMAPMINADDR;
for (i = 0; i < NVMA; ++i) {
if (p->vma[i].addr) {
// get the max address of the mapped memory
addr = max(addr, p->vma[i].addr + p->vma[i].len);
}
}
addr = PGROUNDUP(addr);
if (addr + len > TRAPFRAME) {
return -1;
}
vma->addr = addr;
vma->len = len;
vma->prot = prot;
vma->flags = flags;
vma->offset = offset;
vma->f = f;
filedup(f); // increase the file's reference count
return addr;
}
4. To write page fault Processing code
Because in sys_mmap() The memory for file mapping in is Lazy allocation, Therefore, it is necessary to map the memory generated by accessing the file page fault To deal with . And before Lazy allocation and COW The experiment is the same , I.e. modification kernel/trap.c in usertrap() Code for .
- The first is to add pairs page fault Situational trap The inspection of , Because mapped memory is not allocated , And the memory read and write execution is possible , So all types page fault Can happen , therefore
r_scause()The values of include12,13and15Three situations . - Next is based on the occurrence page fault Address to the current process VMA Find the corresponding... In the array VMA Structure .
What needs to be added here is , according tommapOf Linux manual , File mapping parameterslengthIt can actually exceed the file ( fromoffsetrise ) Size , and File mapping is in pages Of , So the corresponding The part that exceeds the actual size of the file , The content will be 0, You can access and modify , But it will not be written back to the file in the end . But look here VMA The upper limit of memory is throughva < p->vma[i].addr + p->vma[i].lenTo compare ( No increasePGROUNDUP()BecausevaIn advancePGROUNDDOWN()Rounding down ).
Find the corresponding VMA This time page fault It is caused by accessing the memory mapped by the file , And then continue the following work . - And then to Store Page fault Make a separate judgment , Skip here , This part is used for dirty page flag bit (
PTE_D) Set up , In the subsequent unified elaboration . - And then there's going to be Lazy allocation, Use
kalloc()First allocate a physical page , And usememset()To empty ( This step is necessary ,kalloc()The allocated page is initially full0x5Dirty data ). - Then use
readi()According to occurrence page fault The address of reads the contents from the corresponding part of the file to the allocated physical page . The size read here isPGSIZE, If the file size exceedsreadi()The interior will be intercepted , Does not affect . Before and after this process, you need to check the file inode To lock . - After reading the file data , It is very important to set the access permission of this part , Access rights are based on VMA Recorded in the
mmapOfprotParameter to PTE Permission flag bit of . It is relatively simple for read and execute permissions , For write permission, only this time is Store Page fault Will be set when , For specific reasons, see the description of the flag bit of the subsequent dirty page . - Finally, you can use
mappages()Map the physical page to the page value of the user process .
void
usertrap(void)
{
// ...
if(r_scause() == 8){
// ...
} else if (r_scause() == 12 || r_scause() == 13
|| r_scause() == 15) {
// mmap page fault - lab10
char *pa;
uint64 va = PGROUNDDOWN(r_stval());
struct vm_area *vma = 0;
int flags = PTE_U;
int i;
// find the VMA
for (i = 0; i < NVMA; ++i) {
// like the Linux mmap, it can modify the remaining bytes in
//the end of mapped page
if (p->vma[i].addr && va >= p->vma[i].addr
&& va < p->vma[i].addr + p->vma[i].len) {
vma = &p->vma[i];
break;
}
}
if (!vma) {
goto err;
}
// set write flag and dirty flag to the mapped page's PTE
if (r_scause() == 15 && (vma->prot & PROT_WRITE)
&& walkaddr(p->pagetable, va)) {
if (uvmsetdirtywrite(p->pagetable, va)) {
goto err;
}
} else {
if ((pa = kalloc()) == 0) {
goto err;
}
memset(pa, 0, PGSIZE);
ilock(vma->f->ip);
if (readi(vma->f->ip, 0, (uint64) pa, va - vma->addr + vma->offset, PGSIZE) < 0) {
iunlock(vma->f->ip);
goto err;
}
iunlock(vma->f->ip);
if ((vma->prot & PROT_READ)) {
flags |= PTE_R;
}
// only store page fault and the mapped page can be written
//set the PTE write flag and dirty flag otherwise don't set
//these two flag until next store page falut
if (r_scause() == 15 && (vma->prot & PROT_WRITE)) {
flags |= PTE_W | PTE_D;
}
if ((vma->prot & PROT_EXEC)) {
flags |= PTE_X;
}
if (mappages(p->pagetable, va, PGSIZE, (uint64) pa, flags) != 0) {
kfree(pa);
goto err;
}
}
}else if((which_dev = devintr()) != 0){
// ok
} else {
err:
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
// ...
}
5. To write munmap system call
stay kernel/sysfile.c Realize system call in sys_munmap(). According to the requirements of the experiment , The system calls part of the memory to be mapped to unmap , At the same time, if MAP_SHARED You need to Changes to the file mapped memory will be written to the file ( stay Linux in , Being able to write documents is mmap autocomplete , Different from this experiment ).
- The first is the extraction of parameters ,
munmapOnlyaddrandlengthTwo parameters . - Then simply check the parameters .
lenNon negative required ; Besides, according to Linux manual ,addrNeed to bePGSIZEInteger multiple . - Next, the same
usertrap()It's similar to , according toaddrandlengthFind for VMA Structure . If it is not found, it will return failure . - Then it is mainly to judge whether the current unmapped part has
MAP_SHAREDSign a , If so, you need to write this part back to the file . Of course, before that, judgelenIs it 0, If so, it will directly return to success , No follow-up work is required .
This part is the most complex part of the system call , There are two things to consider : First, which pages need to be written , The other is the write size of each write back file . For the former , According to the experimental guidance , Choose to use the dirty page flag bit (PTE_D) For recording , Having this flag indicates that the modified part has been modified , You need to write the page back to the file , For the specific setting of dirty page flag bit, see the following . For the latter , Because the document will be written according to the dirty page , Therefore, it is conceivable that the write size isPGSIZE, But because oflenProbably not.PGSIZEInteger multiple , Judgment is also needed here . Besides , According to the experimental guidance , Reference resourcesfilewrite()function , The size of the file written at a time is also affected by the log block Influence , Therefore, the file may be written in batches ( actuallyPGSIZEGreater thanmaxsz, When the whole page needs to be written, it will be divided into two times ). - After writing the changes back to the file , You can use
uvmunmap()Unmap some changed pages in the user page table .
Here is the method of canceling page table mapping by rounding up the page , Actually, it feels a little unreasonable , Because some pages may not be unmapped , But considering thataddrNeed page alignment , Therefore, the file mapping will not be cancelled in the middle of the page , Otherwise, the latter half of the file cannot be unmapped .
Besides , Modified hereuvmunmap()FunctionalPTE_VCheck part of flag bit , And before Lazy allocation The experiment is the same , Unmapped pages may not be actually allocated , Skip now .
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
// ...
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
if((pte = walk(pagetable, a, 0)) == 0)
panic("uvmunmap: walk");
if((*pte & PTE_V) == 0) {
continue; // lab10
// panic("uvmunmap: not mapped"); // lab10
}
if(PTE_FLAGS(*pte) == PTE_V) {
continue; // lab10
// panic("uvmunmap: not a leaf");
}
// ...
}
}
- Finally, I need to update VMA Structure . Because the unmapped part of the file may just VMA Part of the file mapping memory in the structure , But according to the experimental guidance , The unmapped part will not be in the middle of the file mapping memory , That is, a new block of memory will not be generated due to the cancellation of file mapping , So just update the original VMA The relevant parameters of the structure can be . Throughout VMA When the memory recorded in the structure is unmapped , Then the VMA The structure is empty .
// lab10
uint64 sys_munmap(void) {
uint64 addr, va;
int len;
struct proc *p = myproc();
struct vm_area *vma = 0;
uint maxsz, n, n1;
int i;
if (argaddr(0, &addr) < 0 || argint(1, &len) < 0) {
return -1;
}
if (addr % PGSIZE || len < 0) {
return -1;
}
// find the VMA
for (i = 0; i < NVMA; ++i) {
if (p->vma[i].addr && addr >= p->vma[i].addr
&& addr + len <= p->vma[i].addr + p->vma[i].len) {
vma = &p->vma[i];
break;
}
}
if (!vma) {
return -1;
}
if (len == 0) {
return 0;
}
if ((vma->flags & MAP_SHARED)) {
// the max size once can write to the disk
maxsz = ((MAXOPBLOCKS - 1 - 1 - 2) / 2) * BSIZE;
for (va = addr; va < addr + len; va += PGSIZE) {
if (uvmgetdirty(p->pagetable, va) == 0) {
continue;
}
// only write the dirty page back to the mapped file
n = min(PGSIZE, addr + len - va);
for (i = 0; i < n; i += n1) {
n1 = min(maxsz, n - i);
begin_op();
ilock(vma->f->ip);
if (writei(vma->f->ip, 1, va + i, va - vma->addr + vma->offset + i, n1) != n1) {
iunlock(vma->f->ip);
end_op();
return -1;
}
iunlock(vma->f->ip);
end_op();
}
}
}
uvmunmap(p->pagetable, addr, (len - 1) / PGSIZE + 1, 1);
// update the vma
if (addr == vma->addr && len == vma->len) {
vma->addr = 0;
vma->len = 0;
vma->offset = 0;
vma->flags = 0;
vma->prot = 0;
fileclose(vma->f);
vma->f = 0;
} else if (addr == vma->addr) {
vma->addr += len;
vma->offset += len;
vma->len -= len;
} else if (addr + len == vma->addr + vma->len) {
vma->len -= len;
} else {
panic("unexpected munmap");
}
return 0;
}
6. Dirty page flag bit setting
As mentioned earlier , stay munmap When the system call writes the modified contents in the mapped memory back to the file , Only consider the parts that have been modified , That is, dirty pages are written back to files . This implementation has two benefits , On the one hand, it can reduce the data written back , about MAP_SHARED And writable files , Not all content is written back , Instead, write back only the modified part ; On the other hand , Because the file mapped memory is Lazy allocation, Dirty pages must have allocated memory because they have been modified , Therefore, it is unnecessary to consider whether the current page is actually allocated when unmapping .
stay xv6 in , Dirty pages can be accessed through PTE Dirty page flag bit PTE_D Are identified , however xv6 This part is not implemented by itself . Therefore, the author has simply implemented this part , Only the dirty page flag bit setting of file mapped memory pages is considered .
- The first is
kernel/riscv.hThe dirty page flag bit is defined inPTE_D
#define PTE_D (1L << 7) // dirty flag - lab10
- And then
kernel/vm.cIt definesuvmgetdirty()as well asuvmsetdirtywrite()Two functions . The former is used to read the dirty page flag bit , The latter is used to write dirty page flag bit and write flag bit ( Because the two flag bits are updated at the same time ). Because back PTE Ofwalk()Is an internal function , All the functions defined here are specially used for reading and writing dirty page flag bits .
// get the dirty flag of the va's PTE - lab10
int uvmgetdirty(pagetable_t pagetable, uint64 va) {
pte_t *pte = walk(pagetable, va, 0);
if(pte == 0) {
return 0;
}
return (*pte & PTE_D);
}
// set the dirty flag and write flag of the va's PTE - lab10
int uvmsetdirtywrite(pagetable_t pagetable, uint64 va) {
pte_t *pte = walk(pagetable, va, 0);
if(pte == 0) {
return -1;
}
*pte |= PTE_D | PTE_W;
return 0;
}
- Next is about how to set the dirty page flag bit . The idea is also simple , and COW The mechanism is somewhat similar , It is using page fault Set the dirty page flag bit . For dirty pages , That is, there are modified pages , Its page permissions must be writable , So consider writing a page for the first time by trap Handle the PTE Add the dirty page flag bit . There are two main scenarios :
One is Write to unmapped writable pages , According to the foregoing , At this point will pass trap Allocate physical pages , namely Lazy allocation. Due to triggering this page fault It's a write operation , Therefore, subsequent instructions will certainly modify the content of this page , So add toPTE_DDirty page flag bit .
The other is Read or perform operations on unmapped writable pages , At this time, physical pages will also be allocated , But although the page can be written , But this is a read operation , The content of the page has not been modified , Therefore, you cannot addPTE_DSign a , Add when you need to continue writing . So at this time , and Not set up PTE Write flag bitPTE_W, So the page is currently not writable , If you write to the page later, it will trigger again page fault, At this time, add the write flag bit and dirty page flag bit .
The specific code is in the above steps #4 Ofkernel/trap.cOfusertrap()in . Read / write operations mentioned above , Can passr_scause()The return value of ; And steps #4 Find the corresponding VMA The code behind the structureif (r_scause() == 15 && (vma->prot & PROT_WRITE) && walkaddr(p->pagetable, va))It is used to distinguish the above two situations ,r_scause()by 15 namely Store Page fault, Write operations , here VMA The mapped memory recorded in needs to be equally writable , Andwalkaddr()Return non 0 Value indicates that memory has been allocated to the page . This is the second case used to trigger again page fault Judge when .elseClause is the step #4 Describe the page allocation process , And through codeif (r_scause() == 15 && (vma->prot & PROT_WRITE))Set the dirty page flag bit and write flag bit , Corresponding to the first case above . - stay
usertrap()The dirty page flag bit is set in , Follow upmunmap()You can calluvmgetdirty()To determine whether the page has been modified .
7. modify exit and fork system call
The above content completes the basic mmap and munmap Part of system call . Finally, we need to exit and fork Two system calls to modify , Add memory mapping to process files and VMA Array processing .
- modify
kernel/proc.cMediumexit()function .
When the process exits , Need to look likemunmap()Unmap the memory of the file mapping part . So the added code is similar tomunmap()Part of the basic system , The difference is that you need to traverse VMA Array unmaps all file mapped memory , And the whole part is cancelled .
void
exit(int status)
{
// ...
if(p == initproc)
panic("init exiting");
// unmap the mapped memory - lab10
for (i = 0; i < NVMA; ++i) {
if (p->vma[i].addr == 0) {
continue;
}
vma = &p->vma[i];
if ((vma->flags & MAP_SHARED)) {
for (va = vma->addr; va < vma->addr + vma->len; va += PGSIZE) {
if (uvmgetdirty(p->pagetable, va) == 0) {
continue;
}
n = min(PGSIZE, vma->addr + vma->len - va);
for (r = 0; r < n; r += n1) {
n1 = min(maxsz, n - i);
begin_op();
ilock(vma->f->ip);
if (writei(vma->f->ip, 1, va + i, va - vma->addr + vma->offset + i, n1) != n1) {
iunlock(vma->f->ip);
end_op();
panic("exit: writei failed");
}
iunlock(vma->f->ip);
end_op();
}
}
}
uvmunmap(p->pagetable, vma->addr, (vma->len - 1) / PGSIZE + 1, 1);
vma->addr = 0;
vma->len = 0;
vma->offset = 0;
vma->flags = 0;
vma->offset = 0;
fileclose(vma->f);
vma->f = 0;
}
// Close all open files.
for(int fd = 0; fd < NOFILE; fd++){
if(p->ofile[fd]){
struct file *f = p->ofile[fd];
fileclose(f);
p->ofile[fd] = 0;
}
}
// ...
}
- modify
kernel/proc.cMediumfork()function .
In the use offork()When creating a subprocess , The parent process's VMA Copy the structure , To get the same file mapped memory . Because the file mapping memory is used Lazy allocation, So the processing at this time can be relatively simple , Directly to the parent process VMA Just copy the array , When a child process uses a file to map memory, it will pass page fault Allocate pages .
Of course , Here you can optimize , use COW The mechanism allows parent-child processes to point to the same file to map physical pages , It is very convenient to map memory for non writable files , And the writable memory is through COW The mechanism is updated again . Here the author did not optimize .
int
fork(void)
{
// ...
// increment reference counts on open file descriptors.
for(i = 0; i < NOFILE; i++)
if(p->ofile[i])
np->ofile[i] = filedup(p->ofile[i]);
np->cwd = idup(p->cwd);
// copy all of VMA - lab10
for (i = 0; i < NVMA; ++i) {
if (p->vma[i].addr) {
np->vma[i] = p->vma[i];
filedup(np->vma[i].f);
}
}
safestrcpy(np->name, p->name, sizeof(p->name));
// ...
}
Have a problem
- stay xv6 In the implementation of
mmaptest, among fork_test The following error appears :
solve : according to mmap(4) You can go touser/mmaptest.cWhere the error occurred in , as a result ofmmap()Return failed . Through analysis, it is found that this is a read-only fileMAP_SHAREDForm mapping . At first, the author only judgedMAP_SHAREDFlag bit and file reading and writingmmapCan we continue , But for noPROT_WRITEFile mapping of permissions , Although there areMAP_SHAREDSign a , But it will not be written to the file , Therefore, it should also be possible to continue , Therefore, the correct approach should be to add permissions to the mappingprot & PROT_WRITEThe inspection of , See the above for the specific codesys_mmap()Realization .

test
- stay xv6 In the implementation of
mmaptesttest :
- stay xv6 In the implementation of
userteststest :
- perform
make gradetest :
边栏推荐
猜你喜欢

深度学习:STGCN学习笔记

CFA exam registration instructions

Common commands of database 2

发布自己的npm组件库

Redis网红高频面试题三连:缓存穿透?缓存击穿?缓存雪崩?

Local development using LWC in salesforce

What every Salesforce developer should know about Dates and Times in Apex
![[MIT 6.S081] Lab8: locks](/img/9f/0ff7a0226837a3c420f49e6da8209f.png)
[MIT 6.S081] Lab8: locks

How do corporate giants such as Schneider Electric and L'Oreal make open innovation? Uncover secrets of demo World Innovation Summit

深度学习-论文阅读:动作结构性图卷积网络AS-GCN
随机推荐
Dynamic linked list 4 one-way circular linked list (loopsingle Implementation)
荣耀、小米发双十一战报:都称自己是冠军
黑客用激光攻击,百米外就能激活语音助手
@DateTimeFormat 接收不到时分秒,转换时报类型异常
MySQL solves the problem of insert failure caused by duplicate unique indexes
请教大神一个问题 flinkcdc,同步mysql中的datetime字段会变为时间戳 有人遇到过吗
Salesforce runs all test classes and gets coverage reports
You can't specify target table 'table name' for update in from clause error resolution in MySQL
[learning notes] advanced version of MySQL database - index optimization, slow query, locking mechanism, etc
记一次 .NET 某智慧物流 WCS系统 CPU 爆高分析
软件安装相关
二叉树概念
图形界面编程
深度学习:GAT
Getting started with typora: the most complete tutorial in the whole network
[MIT 6.S081] Lab 9: file system
深度识别:论文阅读_2S-AGCN CVPR2019(基于骨架的动作识别的两流自适应图卷积网络)
【学习笔记】数据库中锁的分类
1542. 找出最长的超赞子字符串 哈希+状态压缩
Marvell公布旗下Arm服务器芯片路线图,下一代性能将比ThunderX2高两倍