一、基础介绍
clone是Linux为创建线程设计的(虽然也可以用clone创建进程)。所以可以说clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。
clone函数功能强大,带了众多参数,它提供了一个非常灵活自由的常见进程的方法。
clone可以让你有选择性的继承父进程的资源,你可以选择想vfork一样和父进程共享一个虚存空间,从而使创造的是线程,
你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。先有必要说下这个函数的结构:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
fn为函数指针,此指针指向一个函数体,即想要创建进程的静态程序(我们知道进程的4要素,这个就是指向程序的指针,就是所谓的“剧本", );
child_stack为给子进程分配系统堆栈的指针(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值);
arg就是传给子进程的参数一般为(0);
flags为要复制资源的标志,描述你需要从父进程继承那些资源(是资源复制还是共享,在这里设置参数:
下面是flags可以取的值
标志 含义
CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE 若父进程被trace,子进程也被trace
CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM 子进程与父进程运行于相同的内存空间
CLONE_PID 子进程在创建时PID与父进程一致
CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
实例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sched.h>
#define FIBER_STACK 8192
int a;
void * stack;
int do_something(){
a=10;
printf("This is son, the pid is:%d, the a is: %d\n", getpid(), a);
free(stack);
exit(1);
}
int main() {
void * stack;
a = 1;
stack = malloc(FIBER_STACK);//为子进程申请系统堆栈
if(!stack) {
printf("The stack failed\n");
exit(0);
}
printf("creating son thread!!!\n");
clone(&do_something, (char *)stack + FIBER_STACK, CLONE_VM|CLONE_VFORK, 0);//创建子线程
printf("This is father, my pid is: %d, the a is: %d\n", getpid(), a);
exit(1);
}
运行的结果:
son的PID:10692;
father的PID:10691;
parent和son中的a都为10;所以证明他们公用了一份变量a,是指针的复制,而不是值的复制。
问题:clone和fork的区别:
(1) clone和fork的调用方式很不相同,clone调用需要传入一个函数,该函数在子进程中执行。
(2)clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。 (void *child_stack,)也就是第二个参数,需要分配栈指针的空间大小,所以它不再是继承或者复制,而是全新的创造。
以上内容参考:
二、实际上,pthread_create()调用的就是clone(),使用比较简单。
#include<pthread.h>
int pthread_create(pthread_t *tidp,
const pthread_attr_t *attr,
(void*)(*start_rtn)(void*),
void *arg);
成功返回0,失败返回-1
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int) pid,
(unsigned int) tid, (unsigned int) tid);
}
void *thr_fn(void *arg)
{
printids("new thread: ");
return NULL;
}
int main(void)
{
int err;
pthread_t ntid;
err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0)
printf("can't create thread: %s\n", strerror(err));
printids("main thread:");
pthread_join(ntid,NULL);
return EXIT_SUCCESS;
}
gcc -o main main.c -lpthread
编译时一定要链接pthread库
三、linux内核clone()函数原型:
asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp,
int __user *parent_tidptr, int tls_val,
int __user *child_tidptr, struct pt_regs *regs)
{
if (!newsp)
newsp = regs->ARM_sp;
return do_fork(clone_flags, newsp, regs, 0, parent_tidptr, child_tidptr);
}
我们可以看到,linux中创建线程的接口使用的是创建进程的do_fork接口,
也足以说明,linux线程本质就是一个进程,只不过拥有自己的栈内存,其余共享父进程的。
线程的实现原理,将在以后文章中提到,这里大概说一下。