标签:shared 导致 launch lock 另一个 tls ext function 进程间
linux 创建线程(pthread_create)和进程(fork)的过程非常类似,都是主要依赖 clone 函数,只不过传入的参数不同而已。
如此一来,内核只需要实现一个 clone函数,就既能创建进程,又能创建线程了,例如;
创建进程:
clone(SIGCHLD)
创建线程:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | SIGCHLD)
其实,linux 内核没有严格区分线程和进程,也没有准备特别的调度算法或是定义特别的数据结构来描述线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程,在 linux 中,线程看起来就像是一个轻量级的进程(light weight process)。
看下clone函数原型:
/* Prototype for the glibc wrapper function */ #include <sched.h> int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ ); /* Prototype for the raw system call */ long clone(unsigned long flags, void *child_stack, void *ptid, void *ctid, struct pt_regs *regs);
glibc clone函数是对clone系统调用的一个封装。
能够看出,clone 函数是一个不定参数的函数。它主要用于创建新的进程(也包括线程,因为线程是“特殊”的进程),调用成功后,返回子进程的 tid,如果失败,则返回 -1,并将错误码设置再 errno。
clone 函数的第1个参数fn是一个函数指针;第2个参数child_stack是用于创建子进程的栈(注意需要将栈的高地址传入);第3个参数flags,就是用于指定行为的参数了。
flags参数列举如下:
看个用clone() 创建线程的例子
//#define _GNU_SOURCE #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <time.h> #include <sys/time.h> #include <unistd.h> #include <sys/syscall.h> #define STACK_SIZE 8192*1024 #define gettid() ((int)syscall(SYS_gettid)) void* testOcupMem(void* p) { printf(" %d-%d testOcupMem exit\n", getpid(), gettid()); usleep(100); return NULL; } int main() { void* pstk = malloc(STACK_SIZE); if (NULL == pstk){ printf("cannot malloc stack\n"); return -1; } printf("main: %d-%d\n", getpid(), gettid()); for (int i = 0; i < 5; i++) { int clonePid = clone((int(*)(void*))testOcupMem, (char *)pstk + STACK_SIZE, CLONE_VM | CLONE_FS | CLONE_FILES | SIGCHLD, NULL); if (-1 == clonePid) { printf("query failed, cannot start query process\n"); free(pstk); return -1; } struct timeval val; gettimeofday(&val, 0); printf("%d:%ld clonePid: %d-%d\n\n", i, val.tv_sec * 1000 + val.tv_usec / 1000, getpid(), clonePid); usleep(1000 * 1000); } free(pstk); printf("\n------------- getchar --------------\n"); getchar(); return 0; }
说明:
输出结果:
main: 28942-28942
0:1606723498039 clonePid: 28942-28943
28943-28943 testOcupMem exit
1:1606723499039 clonePid: 28942-28945
1:1606723499039 clonePid: 28942-28945
28945-28945 testOcupMem exit
2:1606723500039 clonePid: 28942-28986
2:1606723500039 clonePid: 28942-28986
28986-28986 testOcupMem exit
3:1606723501039 clonePid: 28942-28989
3:1606723501039 clonePid: 28942-28989
28989-28989 testOcupMem exit
4:1606723502039 clonePid: 28942-29008
4:1606723502039 clonePid: 28942-29008
29008-29008 testOcupMem exit
从输出内容可见:
1、在testOcupMem函数打印的getpid都是同一个值,说明每次clone创建的线程属于同一个父进程(main进程),但 tid 各不相同;
2、为什么main函数for循环的 clonePid 日志会重复2次呢(看时间戳应该是同一个时间点打印的)?据我感觉应该是clone调用会在一个新线程开始执行testOcupMem函数,而这个函数如果后于printf执行,可能创建的新线程也执行了printf,这样当main线程打印的时候就有2条日志。当然,这个只是我的猜测,还有待验证。
PS:如果想用clone创建进程,在上面的例子中,把clone函数的flags参数去掉 CLONE_VM | CLONE_FS | CLONE_FILES 即可。
mmap函数把一个文件或一个POSIX共享内存区对象映射到调用进程的地址空间,使用该函数有3个目的:
1、使用普通文件以提供内存映射I/O;
2、使用特殊文件以提供匿名内存映射;
3、使用shm_open以提供无亲缘关系进程间的POSIX共享内存区。
存储映射I/O使一个磁盘文件的全部或部分内容映射到用户空间中,将进程读写文件的操作变成了读写内存的操作(不再需要read/write调用)。
API:
#include <sys/mman.h> void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off); int mprotect(void *addr, size_t len, int prot); int msync(void *addr, size_t len, int flags); int munmap(void *addr, size_t len);
说明:
mmap函数中的prot参数
PROT_READ |
映射区可读 |
PROT_WRITE |
映射区可写 |
PROT_EXEC |
映射区可执行 |
PROT_NONE |
映射区不可访问 |
mmap函数中的flags参数
MAP_PRIVATE |
私有,对映射区的写操作会导致创建映射文件的一个私有副本 |
MAP_SHARED |
对映射区的写操作直接修改原始文件,多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化 |
MAP_FIXED |
返回值必须等于addr,不利于移植性 |
MAP_ANONYMOUS |
匿名映射,此时忽略fd,且映射区域无法与其它进程共享,这个选项一般用来扩展heap |
MAP_DENYWRITE |
|
MAP_LOCKED |
msync函数中的flags参数,
MS_ASYNC |
异步写 |
MS_SYNC |
同步写 |
MS_INVALIDATE |
从文件中读回数据 |
注意:调用fork函数,子进程继承存储映射区(子进程复制父进程的地址空间,而存储映射区是该地址空间的一部分),但调用exec之后的程序则不继承此存储映射区。
如下,fd=0,没有关联任何文件;
void annoymap(int N) { int *ptr = (int *) mmap(NULL, N * sizeof(int), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); if (ptr == MAP_FAILED) { printf("Mapping Failed\n"); return; } // Fill the elements of the array for (int i = 0; i < N; i++) { ptr[i] = i; } // print for (int i = 0; i < N; i++) { printf("[%d] ", ptr[i]); } printf("\n"); if (0 != munmap(ptr, 10 * sizeof(int))) { printf("UnMapping Failed\n"); return; } }
如下,以MAP_SHARED 映射指定文件,修改后可以同步到文件中;
void filemap(const char* path) { int fd; void *start; struct stat sb; fd = open(path, O_RDWR); fstat(fd, &sb); start = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (start == MAP_FAILED) { printf("mmap failed\n"); return ; } close(fd); printf("%s", start); strncpy((char*)start, "say hi", 6); munmap(start, sb.st_size); }
多进程之间共享
void sharememory(int size) { char parent_message[] = "hello"; void* shmem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); memcpy(shmem, parent_message, sizeof(parent_message)); if (fork() == 0) { printf("Child read: %s\n", shmem); char child_message[] = "goodbye"; memcpy(shmem, child_message, sizeof(child_message)); printf("Child wrote: %s\n", shmem); } else { printf("Parent read: %s\n", shmem); sleep(1); printf("After 1s, parent read: %s\n", shmem); } }
参考:
https://eli.thegreenplace.net/2018/launching-linux-threads-and-processes-with-clone/
标签:shared 导致 launch lock 另一个 tls ext function 进程间
原文地址:https://www.cnblogs.com/chenny7/p/14060906.html