一、基础介绍
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线程本质就是一个进程,只不过拥有自己的栈内存,其余共享父进程的。
线程的实现原理,将在以后文章中提到,这里大概说一下。