PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

前置说明

  本文作为本人csdn blog的主站的备份。(BlogID=081)

  本文发布于 2019-03-12 17:18:23,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=081)

环境说明

  无

前言


  无

背景

  没有买卖,就没有伤害。 ------ (佚名)

  作为一个打工仔,要积极完成领导分配的任务。so,我分配到一个关于android进程间高效传输大量数据的的任务。不用我说,只要提及“大量”“高效”“进程间”这几个词,首先就得想到共享内存。虽然共享内存有这样那样的缺点,但是有一个优点,那就是快。

  可是这里有个问题,为什么我需要去读这些驱动或者乱七八糟的东西?因为Android提供了一个java类叫做:MemoryFile就可以实现共享内存,MemoryFile是android基于其共享内存机制实现的java层到java层之间的数据传输。但是,在我们的工作内容中,需要在android系统中用前向框架跑深度学习算法,这里有个问题,我们跑算法的时候是在C or C++层跑的,但是我们有一些图像数据需要在apk里面采集或者使用。在Android中,怎么让图像数据在c or c++层和java层快速传输,c or c++ 层之间快速传输,这就需要我们了解android 共享内存机制,做出合适的抉择。

Android Anonymous Shared Memory 简介


  我们都知道Android是基于Linux内核搭建的一个分层系统,其中有kernel层,RunTimeLib和其他lib层,Framework层,Application层。虽然Linux os带了很多IPC的机制,其中Sharedmemory也有两种,但是在android系统里面,是没有这些内容的,android的linuxkernel你可以理解为是一种深度定制版,很多东西都与普通的linux kenel不一致。

  由于Androidkernel不带普通的linuxkernel的常用共享内存方式,所以android 系统提供了另外一种替代方式,当然不是全部重复造轮子,只是通过驱动程序的方式,增加了自己想要的新特性,并封装了一个新的共享内存接口出来。

  更详细和基本的介绍:请大家去参考百度的很多对Android共享内存的介绍。现在网上有很多关于android共享内存的介绍,有部分是精品,让我受益匪浅,是可以参考的。这里向这些无私奉献精品的前辈致敬。

Android Anonymous Shared Memory 驱动源码分析


  在我记忆中,有一个不知道谁说的,学习的好方法,那就是:从源码来,到源码去。我一直抱着这种心态来学习新的事物,毕竟许多理论是需要我们去了解,虽然不需要去重复造轮子。

  Android kernel的驱动编写和linuxkernel的驱动编写类似,都是有一个入口函数。我们首先从这两个函数开始分析,然后分析几个比较重要的接口就行。

  源码版本:android-p release kernel 4.9

  目录:\drivers\staging\android\ashmem.c

  驱动入口

/**
* struct ashmem_area - The anonymous shared memory area
* @name: The optional name in /proc/pid/maps
* @unpinned_list: The list of all ashmem areas
* @file: The shmem-based backing file
* @size: The size of the mapping, in bytes
* @prot_mask: The allowed protection bits, as vm_flags
*
* The lifecycle of this structure is from our parent file's open() until
* its release(). It is also protected by 'ashmem_mutex'
*
* Warning: Mappings do NOT pin this structure; It dies on close()
*/
struct ashmem_area {
char name[ASHMEM_FULL_NAME_LEN];
struct list_head unpinned_list;
struct file *file;
size_t size;
unsigned long prot_mask;
}; /**
* struct ashmem_range - A range of unpinned/evictable pages
* @lru: The entry in the LRU list
* @unpinned: The entry in its area's unpinned list
* @asma: The associated anonymous shared memory area.
* @pgstart: The starting page (inclusive)
* @pgend: The ending page (inclusive)
* @purged: The purge status (ASHMEM_NOT or ASHMEM_WAS_PURGED)
*
* The lifecycle of this structure is from unpin to pin.
* It is protected by 'ashmem_mutex'
*/
struct ashmem_range {
struct list_head lru;
struct list_head unpinned;
struct ashmem_area *asma;
size_t pgstart;
size_t pgend;
unsigned int purged;
}; static const struct file_operations ashmem_fops = {
.owner = THIS_MODULE,
.open = ashmem_open,
.release = ashmem_release,
.read = ashmem_read,
.llseek = ashmem_llseek,
.mmap = ashmem_mmap,
.unlocked_ioctl = ashmem_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_ashmem_ioctl,
#endif
}; static struct miscdevice ashmem_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ashmem",
.fops = &ashmem_fops,
}; static struct kmem_cache *ashmem_area_cachep __read_mostly;
static struct kmem_cache *ashmem_range_cachep __read_mostly; static int __init ashmem_init(void)
{
int ret = -ENOMEM;
//slab 缓存 中 创建struct ashmem_area内存区域结构,这个创建后,下一次分配这个结构体的时候可以更快。
ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
sizeof(struct ashmem_area),
0, 0, NULL);
////unlikely()--执行else后面的语句概率较高,增加cache命中率,likely()与此功能相反
//
if (unlikely(!ashmem_area_cachep)) {
pr_err("failed to create slab cache\n");
goto out;
}
//同上
ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
sizeof(struct ashmem_range),
0, 0, NULL);
if (unlikely(!ashmem_range_cachep)) {
pr_err("failed to create slab cache\n");
goto out_free1;
}
//杂项设备注册
ret = misc_register(&ashmem_misc);
if (unlikely(ret)) {
pr_err("failed to register misc device!\n");
goto out_free2;
}
//这个好像和内存回收有关。我这里不关心。
register_shrinker(&ashmem_shrinker); pr_info("initialized\n"); return 0; out_free2:
kmem_cache_destroy(ashmem_range_cachep);
out_free1:
kmem_cache_destroy(ashmem_area_cachep);
out:
return ret;
} device_initcall(ashmem_init);

  当这个设备创建以后,就会在/dev/下生成一个ashmem字符设备。

  在struct file_operations ashmem_fops中,我们注册了很多接口,如果大家对linux 驱动编程没有一点了解的话,你可以直接理解为我们在用户态调用open就会调用这里的ashmem_open,其他的类似。

  下面我们重点介绍几个我们常用的接口:ashmem_open,ashmem_ioctl,ashmem_mmap。

  ashmem_open

  我们调用open的时候,打开一个内存共享。

/**
* ashmem_open() - Opens an Anonymous Shared Memory structure
* @inode: The backing file's index node(?)
* @file: The backing file
*
* Please note that the ashmem_area is not returned by this function - It is
* instead written to "file->private_data".
*
* Return: 0 if successful, or another code if unsuccessful.
*/
static int ashmem_open(struct inode *inode, struct file *file)
{
struct ashmem_area *asma;
int ret; //检查vfs打开的文件,在32为系统下打开大文件导致overflow的问题
ret = generic_file_open(inode, file);
////unlikely()--ret为0的概率较大,增加cache命中率,方便编译器优化分支语句。
//(把else后的语句紧贴if之后,把return 放到jmp之后。)likely()与此功能相反
//
if (unlikely(ret))
return ret; /* GFP_KERNEL —— 正常分配内存,可以被中断,还有其他内存分配标志。GFP_KERNEL,GFP_DMA */
//快速分配一个struct ashmem_area结构体,相当于这段新开辟的共享内存的句柄
asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
if (unlikely(!asma))
return -ENOMEM;
//这个和内存回收有关,不管
INIT_LIST_HEAD(&asma->unpinned_list);
//给共享内存名字赋初值
memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);
//设置保护位
asma->prot_mask = PROT_MASK;
//利用struct file结构体中的private_data 来保存我们刚刚分配的句柄的指针。private_data 可以用来携带个人定制的数据,这里用来携带我们定义的共享内存句柄。注意这个地方很重要,为啥重要后面独立解释。
file->private_data = asma; return 0;
}

  ashmem_ioctl

  这里我们关注一下,给struct ashmem_area 的name和size赋值即可,这里注意,还没有分配实际的内存,仅仅是句柄的相关信息填充完毕了。

static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
//这里就是获取之前ashmem_open的过程中,我们保存的共享内存句柄
struct ashmem_area *asma = file->private_data;
long ret = -ENOTTY; switch (cmd) {
case ASHMEM_SET_NAME:
ret = set_name(asma, (void __user *)arg);
break;
case ASHMEM_GET_NAME:
ret = get_name(asma, (void __user *)arg);
break;
case ASHMEM_SET_SIZE:
ret = -EINVAL;
mutex_lock(&ashmem_mutex);
if (!asma->file) {
ret = 0;
asma->size = (size_t)arg;
}
mutex_unlock(&ashmem_mutex);
break;
case ASHMEM_GET_SIZE:
ret = asma->size;
break;
case ASHMEM_SET_PROT_MASK:
ret = set_prot_mask(asma, arg);
break;
case ASHMEM_GET_PROT_MASK:
ret = asma->prot_mask;
break;
case ASHMEM_PIN:
case ASHMEM_UNPIN:
case ASHMEM_GET_PIN_STATUS:
ret = ashmem_pin_unpin(asma, cmd, (void __user *)arg);
break;
case ASHMEM_PURGE_ALL_CACHES:
ret = -EPERM;
if (capable(CAP_SYS_ADMIN)) {
struct shrink_control sc = {
.gfp_mask = GFP_KERNEL,
.nr_to_scan = LONG_MAX,
};
ret = ashmem_shrink_count(&ashmem_shrinker, &sc);
ashmem_shrink_scan(&ashmem_shrinker, &sc);
}
break;
} return ret;
}

  ashmem_mmap

static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
//同上
struct ashmem_area *asma = file->private_data;
int ret = 0; mutex_lock(&ashmem_mutex); /* user needs to SET_SIZE before mapping */
if (unlikely(!asma->size)) {
ret = -EINVAL;
goto out;
} /* requested mapping size larger than object size */
if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {
ret = -EINVAL;
goto out;
} /* requested protection bits must match our allowed protection mask */
if (unlikely((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
calc_vm_prot_bits(PROT_MASK, 0))) {
ret = -EPERM;
goto out;
}
vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask); //第一次mmap时,正式申请内存
if (!asma->file) {
char *name = ASHMEM_NAME_DEF;
struct file *vmfile; if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')
name = asma->name; /* ... and allocate the backing shmem file */ /**
* shmem_kernel_file_setup - get an unlinked file living in tmpfs which must be
* kernel internal. There will be NO LSM permission checks against the
* underlying inode. So users of this interface must do LSM checks at a
* higher layer. The users are the big_key and shm implementations. LSM
* checks are provided at the key or shm level rather than the inode.
* @name: name for dentry (to be seen in /proc/<pid>/maps
* @size: size to be set for the file
* @flags: VM_NORESERVE suppresses pre-accounting of the entire object size
*/
//在tmpfs中创建一个文件,并创建一个inode指向这个文件,并把inode和struct file的返回值关联起来。这个文件就是我们实际的共享的内存文件。这里我们看到其实android 匿名共享内存也是基于linux 普通的共享内存底层来实现的,不重复造轮子。
vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
if (IS_ERR(vmfile)) {
ret = PTR_ERR(vmfile);
goto out;
}
vmfile->f_mode |= FMODE_LSEEK;
//用file域保存我们在tmpfs中创建的共享内存文件的file结构指针。也就是说现在开始,asma->file指向了我们的共享内存文件。
asma->file = vmfile;
} get_file(asma->file); //把asma->file和我们mmap 的内存区域中的vma->vm_file关联起来。这样访问mmap的这段内存区域就等于访问我们创建的这个共享内存文件。
if (vma->vm_flags & VM_SHARED)
shmem_set_file(vma, asma->file);
else {
if (vma->vm_file)
fput(vma->vm_file);
vma->vm_file = asma->file;
} out:
mutex_unlock(&ashmem_mutex);
return ret;
}

Android Anonymous Shared Memory 驱动使用


  Android 官方的一个lib用例库中,封装了一部分共享内存的用法,这部分内容就是给MemoryFile的Native层的库使用的。下面我们一起来分析分析。

  Android O r4

  system/core/libcutils/ashmem-dev.c

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ /*
* Implementation of the user-space ashmem API for devices, which have our
* ashmem-enabled kernel. See ashmem-sim.c for the "fake" tmp-based version,
* used by the simulator.
*/
#define LOG_TAG "ashmem" #include <errno.h>
#include <fcntl.h>
#include <linux/ashmem.h>
#include <pthread.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h> #include <cutils/ashmem.h>
#include <log/log.h> #define ASHMEM_DEVICE "/dev/ashmem" /* ashmem identity */
static dev_t __ashmem_rdev;
/*
* If we trigger a signal handler in the middle of locked activity and the
* signal handler calls ashmem, we could get into a deadlock state.
*/
static pthread_mutex_t __ashmem_lock = PTHREAD_MUTEX_INITIALIZER; /* logistics of getting file descriptor for ashmem */
static int __ashmem_open_locked()
{
int ret;
struct stat st;
//这里打开了"/dev/ashmem"驱动设备,创建了一个共享内存文件,返回了这个共享文件的文件描述符
int fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR));
if (fd < 0) {
return fd;
} ret = TEMP_FAILURE_RETRY(fstat(fd, &st));
if (ret < 0) {
int save_errno = errno;
close(fd);
errno = save_errno;
return ret;
}
if (!S_ISCHR(st.st_mode) || !st.st_rdev) {
close(fd);
errno = ENOTTY;
return -1;
} __ashmem_rdev = st.st_rdev;
return fd;
} static int __ashmem_open()
{
int fd; pthread_mutex_lock(&__ashmem_lock);
fd = __ashmem_open_locked();
pthread_mutex_unlock(&__ashmem_lock); return fd;
} /* Make sure file descriptor references ashmem, negative number means false */
static int __ashmem_is_ashmem(int fd, int fatal)
{
dev_t rdev;
struct stat st; if (TEMP_FAILURE_RETRY(fstat(fd, &st)) < 0) {
return -1;
} rdev = 0; /* Too much complexity to sniff __ashmem_rdev */
if (S_ISCHR(st.st_mode) && st.st_rdev) {
pthread_mutex_lock(&__ashmem_lock);
rdev = __ashmem_rdev;
if (rdev) {
pthread_mutex_unlock(&__ashmem_lock);
} else {
int fd = __ashmem_open_locked();
if (fd < 0) {
pthread_mutex_unlock(&__ashmem_lock);
return -1;
}
rdev = __ashmem_rdev;
pthread_mutex_unlock(&__ashmem_lock); close(fd);
} if (st.st_rdev == rdev) {
return 0;
}
} if (fatal) {
if (rdev) {
LOG_ALWAYS_FATAL("illegal fd=%d mode=0%o rdev=%d:%d expected 0%o %d:%d",
fd, st.st_mode, major(st.st_rdev), minor(st.st_rdev),
S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IRGRP,
major(rdev), minor(rdev));
} else {
LOG_ALWAYS_FATAL("illegal fd=%d mode=0%o rdev=%d:%d expected 0%o",
fd, st.st_mode, major(st.st_rdev), minor(st.st_rdev),
S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IRGRP);
}
/* NOTREACHED */
} errno = ENOTTY;
return -1;
} int ashmem_valid(int fd)
{
return __ashmem_is_ashmem(fd, 0) >= 0;
} /*
* ashmem_create_region - creates a new ashmem region and returns the file
* descriptor, or <0 on error
*
* `name' is an optional label to give the region (visible in /proc/pid/maps)
* `size' is the size of the region, in page-aligned bytes
*/
//实际我们要创建的共享内存方法就是这个,其实就是打开设备,然后设置name和size,最终通过mmap把这个fd映射到我们的进程空间,然后我们的程序就可以访问了。
int ashmem_create_region(const char *name, size_t size)
{
int ret, save_errno; int fd = __ashmem_open();
if (fd < 0) {
return fd;
} if (name) {
char buf[ASHMEM_NAME_LEN] = {0}; strlcpy(buf, name, sizeof(buf));
ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf));
if (ret < 0) {
goto error;
}
} ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));
if (ret < 0) {
goto error;
} return fd; error:
save_errno = errno;
close(fd);
errno = save_errno;
return ret;
} int ashmem_set_prot_region(int fd, int prot)
{
int ret = __ashmem_is_ashmem(fd, 1);
if (ret < 0) {
return ret;
} return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_PROT_MASK, prot));
} int ashmem_pin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len }; int ret = __ashmem_is_ashmem(fd, 1);
if (ret < 0) {
return ret;
} return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin));
} int ashmem_unpin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len }; int ret = __ashmem_is_ashmem(fd, 1);
if (ret < 0) {
return ret;
} return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_UNPIN, &pin));
} int ashmem_get_size_region(int fd)
{
int ret = __ashmem_is_ashmem(fd, 1);
if (ret < 0) {
return ret;
} return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL));
}

  同时,在MemoryFile的jni接口中,我们可以发现直接调用ashmem_create_region,创建了一块共享内存区域。

static jobject SharedMemory_create(JNIEnv* env, jobject, jstring jname, jint size) {

    // Name is optional so we can't use ScopedUtfChars for this as it throws NPE on null
const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr; int fd = ashmem_create_region(name, size); // Capture the error, if there is one, before calling ReleaseStringUTFChars
int err = fd < 0 ? errno : 0; if (name) {
env->ReleaseStringUTFChars(jname, name);
} if (fd < 0) {
throwErrnoException(env, "SharedMemory_create", err);
return nullptr;
} return jniCreateFileDescriptor(env, fd);
}

后记


  总结

  这里我们看到,共享内存的创建原理。但是这里有个问题没有解释清楚,那就是内存是怎么共享的?

  在本文中,我们可以知道我们的创建共享内存的进程可以得到一个共享内存文件的文件描述符,其他的进程怎么知道这块内存在那里呢?这里我明确说明,android还要靠共享共享内存文件文件描述符来实现共享内存,但是一个文件描述符只对当前进程有效,其他进程的同一个值的文件描述符可能指向不同的文件,所以得有一种可靠的方式来实现文件描述符的传递即可。

  预知后事如何,请听下回分解!!

2019/3/18更新

  本来是一系列的文章,但是后续篇整理后,发现不能成为一个系列了。所以这里留个传送门:

《linux kernel 中进程间描述符的传递方法及原理》:https://blog.csdn.net/u011728480/article/details/88553602

参考文献


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

Android匿名共享内存(Anonymous Shared Memory) --- 瞎折腾记录 (驱动程序篇)的更多相关文章

  1. Android 匿名共享内存Java接口分析

    在Android 匿名共享内存驱动源码分析中介绍了匿名共享内存的驱动实现过程,本文在Android匿名共享内存驱动基础上,介绍Android匿名共享内存对外Android系统的匿名共享内存子系统的主体 ...

  2. Android 匿名共享内存C++接口分析

    在上一篇Android 匿名共享内存C接口分析中介绍了Android系统的匿名共享内存C语言访问接口,本文在前文的基础上继续介绍Android系统的匿名共享内存提供的C++访问接口.在C++层通过引入 ...

  3. Android 匿名共享内存C接口分析

    在Android 匿名共享内存驱动源码分析中详细分析了匿名共享内存在Linux内核空间的实现,虽然内核空间实现了匿名共享内存,但仍然需要在用户空间为用户使用匿名共享内存提供访问接口.Android系统 ...

  4. 【并行计算-CUDA开发】关于共享内存(shared memory)和存储体(bank)的事实和疑惑

    关于共享内存(shared memory)和存储体(bank)的事实和疑惑 主要是在研究访问共享内存会产生bank conflict时,自己产生的疑惑.对于这点疑惑,网上都没有相关描述, 不管是国内还 ...

  5. CUDA学习(五)之使用共享内存(shared memory)进行归约求和(一个包含N个线程的线程块)

    共享内存(shared memory)是位于SM上的on-chip(片上)一块内存,每个SM都有,就是内存比较小,早期的GPU只有16K(16384),现在生产的GPU一般都是48K(49152). ...

  6. 共享内存(shared memory)

    共享内存指在多处理器的计算机系统中,可以被不同中央处理器(CPU)访问的大容量内存.由于多个CPU需要快速访问存储器,这样就要对存储器进行缓存(Cache). 任何一个缓存的数据被更新后,由于其他处理 ...

  7. CUDA学习(六)之使用共享内存(shared memory)进行归约求和(M个包含N个线程的线程块)

    在https://www.cnblogs.com/xiaoxiaoyibu/p/11402607.html中介绍了使用一个包含N个线程的线程块和共享内存进行数组归约求和, 基本思路: 定义M个包含N个 ...

  8. IPC最快的方式----共享内存(shared memory)

    在linux进程间通信的方式中,共享内存是一种最快的IPC方式.因此,共享内存用于实现进程间大量的数据传输,共享内存的话,会在内存中单独开辟一段内存空间,这段内存空间有自己特有的数据结构,包括访问权限 ...

  9. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6666491 在前面一篇文章Android系统匿 ...

  10. Fresco内存机制(Ashmem匿名共享内存)

    Fresco的内存机制 Fresco是Facebook出品的高性能图片加载库,采用了Ashmem匿名共享内存机制, 来解决图片加载中的OOM问题.这里不对Fresco做深入分析,只关注Fresco在A ...

随机推荐

  1. Web入门:JavaScript文字动画

    欢迎来的我的小院,恭喜你今天又要涨知识了! 案例内容 利用JavaScript实现文字逐步展现的动画效果. 演示 学习 <!DOCTYPE html> <html lang=&quo ...

  2. Eclipse安装配置、卸载教程(Windows版)

    Eclipse是一个开放源代码的集成开发环境(IDE),最初由IBM公司开发,现在由Eclipse基金会负责维护.它是一个跨平台的工具,可以用于开发多种编程语言,如Java.C/C++.Python. ...

  3. 从函数柯里化聊到add(1)(2)(3) add(1, 2)(3),以及柯里化无限调用

    壹 ❀ 引 很久之前看到过的一道面试题,最近复习又遇到了,这里简单做个整理,本题考点主要是函数柯里化,所以在实现前还是简单介绍什么是柯里化. 贰 ❀ 函数柯里化(Currying) 所谓函数柯里化,其 ...

  4. 从零开始的微信小程序入门教程(四),理解小程序事件与冒泡机制

    壹 ❀ 引 我在之前初识WXML与数据绑定两篇文章中,介绍了小程序静态模板与样式相关概念,以及小程序几种常用数据绑定方式,在知道这些知识后,我们可以写一些不算复杂的小程序页面,并能将一些自定义的数据渲 ...

  5. NC223888 红色和紫色.md

    题目链接 题目 题目描述 漫长的生命总是无聊的.这天,小红和紫准备玩一个染色游戏. 她们拿出了一个有 \(n*m\) 个格子的网格,每个格子只能被染成红色或紫色.每个人可以任意选择一个格子染成红色和紫 ...

  6. 吴X凡绯闻女友小怡同学被骂到清空社交平台?各大平台连敏感词库都没有的吗?

    敏感词都没有的平台 最近某加拿大籍贯的 rapper 被曝私生活不检点,且极有可能涉及诱X未成年少女,成为一个 raper. 当然至于是否属实,其实一个人是否是海王,微信.QQ 聊天记录里面记得清清楚 ...

  7. STM32F401的PWM输出

    PWM的说明 PWM有三个关键指标: PWM频率, 占空比, 区分度 对于同一个时钟频率下工作的单片机, 区分度是和PWM工作频率相关的, 因为总频率是固定的, PWM工作频率越高, 留下给区分度的部 ...

  8. 【Unity3D】使用GL绘制线段

    1 前言 ​ 线段渲染器LineRenderer.拖尾TrailRenderer.绘制物体表面三角形网格从不同角度介绍了绘制线段的方法,本文再介绍一种新的绘制线段的方法:使用 GL 绘制线段. ​ G ...

  9. Uniapp+Nodejs实现外卖App项目1-项目介绍

    项目介绍 本项目采用uniapp和nodejs(数据接口).mongodb等技术实现了一个类似美团外卖的简易APP.项目主要目的是为了快速上手,如何快速使用uniapp开发一个app项目,同时掌握一些 ...

  10. win32 - 将线程重定向到另一个函数(附带Suspend的解释)

    Suspend: 挂起指定的线程 备注:不要永远挂起线程, 因为在Win32中,进程堆是线程安全的对象,并且由于在不访问堆的情况下很难在Win32中完成很多工作,因此在Win32中挂起线程极有可能使进 ...