clone()

本文主要介绍了Linux中的clone函数,它是fork的升级版本,可创建进程或线程,还能指定创建新命名空间、有选择地继承父进程内存等。同时提到pthread_create()调用的就是clone(),且Linux线程本质是进程,有自己的栈内存,其余共享父进程的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、基础介绍
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,)也就是第二个参数,需要分配栈指针的空间大小,所以它不再是继承或者复制,而是全新的创造。

以上内容参考:https://blog.youkuaiyun.com/gogokongyin/article/details/51178257

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

### C# 和 PHP 中的 Clone 操作 #### C# 的 `Clone` 方法详解 在 C# 编程语言中,提供了 `Clone` 方法用于对象复制。此方法分为浅拷贝和深拷贝两种形式[^1]。 - **浅拷贝**:仅复制对象本身及其值类型的字段;对于引用类型,则只复制引用而不创建新的实例。 - **深拷贝**:不仅复制对象本身以及其内部的所有成员变量(无论是值类型还是引用类型),还会递归地复制这些成员所指向的对象。 为了实现更复杂的克隆行为,在类中可以重写 `MemberwiseClone()` 函数或者利用序列化机制完成完整的深拷贝过程。 ```csharp public class Person : ICloneable { public string Name { get; set; } public int Age { get; set; } // 实现ICloneable接口的方法 public object ShallowCopy() => this.MemberwiseClone(); // 自定义深拷贝逻辑 public object DeepCopy(){ using (var ms = new MemoryStream()){ var formatter = new BinaryFormatter(); formatter.Serialize(ms, this); ms.Position = 0; return (Person)formatter.Deserialize(ms); } } } ``` #### PHP 的 `__clone` 魔术方法解析 而在 PHP 中则存在名为 `__clone` 的魔术函数[^2]: 每当使用关键字 `clone` 创建某个对象的新副本时就会触发这个特殊的方法被执行。开发者可以在其中加入额外处理步骤以确保新旧两个实体间的关系正确无误——比如调整某些依赖项或是重新分配随机数种子等操作。 ```php class ExampleClass { private $property; function __construct($value){ $this->property = $value; } // 定义__clone方法来进行必要的初始化工作 public function __clone(){ // 这里可以根据需要修改属性或执行其他动作 $this->property .= ' cloned'; } } $originalObject = new ExampleClass('Original'); $newInstance = clone $originalObject; echo $newInstance->property . "\n"; // 输出:"Original cloned" ``` #### 应用场景举例 当面对频繁重复相似任务的情况时,预先构建好基础模板之后再基于它派生出多个变体不失为一种明智的选择[^3]。例如在一个图形编辑器应用里面绘制多边形形状的时候,先建立一个通用框架随后依据不同参数微调各个顶点位置即可快速生成一系列略有差别的几何图案而无需每次都从头做起。 另外值得注意的是尽管两者都能达到创造新个体的目的但是由于各自的工作原理有所区别因此适用范围也存在一定差异[^4]。通常来说如果希望获得完全独立互不干扰的数据结构那么应当优先考虑采用深拷贝策略;反之若是仅仅出于性能优化考量想要节省内存空间的话则可以选择效率更高的浅层复制方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值