MySpace

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  18 随笔 :: 2 文章 :: 10 评论 :: 0 Trackbacks

    在我们需要一个程序成为后台的守护进程时,一般是通过fork 来创建一个子进程,随之父进程结束,然后再通过 setsid 来使子进程脱离父进程所属的进程组和会话。(通俗一点也可以这么认为,我们需要给这个已经长大的孩子重新找个家,并且跟之前家庭断绝关系,跟人有点类似,虽然关系断绝了,但亲爹是谁这个事实是谁也改变不了的,所以不管发生什么,亲爹的ID 号是不会变的。因为之前的父母或家庭都有可能被灭亡导致这个孩子因为没有家也被饿死。)这样才能不至于当终端发送 Ctrl+c或是该会话中断后守护进程也随之结束,从而达到守护的目的。

    我们一般在写程序时都有这样的习惯,先是初始化,然后再开始工作。但是在使用 fork 的时候这一点就要注意了。创建子进程的时候系统会从父进程中将数据拷贝一份到子进程中,如果 fork之前我们 new 或者 malloc 了一块内存,并且保存了一个指向该内存的指针,那么在创建子进程的时候,也会将这个指针拷贝过去,但此处只拷贝了指针并没有拷贝内存块。所以当父进程结束的时候这块内存会被释放,那么此时在子进程中的指针则指向了一块已经被释放的内存了。有一段这样的代码

 

void test15()

{

    class A

    {

    public:

        A():pid_(0)

        {

            p_malloc = (char*)malloc(100);

            printf("pro p_malloc_ = %d \n",p_malloc_);

        }

        ~A()

        {

            printf("free(p_malloc_) , p_malloc = %d,pid = %d\n", p_malloc_, pid_);

            free(p_malloc);

        }

        A(const A& a)

        {

            printf("A(const A& a) \n");

            p_malloc_ = a.p_malloc_;

        }

        void set_pid(int pid)

        {

            cout << "set pid" << endl;

            printf("set pid,pid = %d\n", pid);

            pid_ = pid;

        }

    public:

        char *p_malloc_;

        int pid_;

 

    };

 

    A a;

 

    pid_t pid;

    pid = fork();

    a.set_pid(pid);

    switch(pid)

    {

    case -1:

        cout << "fork() is error" << endl;

        exit(-1);

        break;

    case 0:

        //exit(0);

        break;

    default:

        setsid();

        pid_t pre_pid = getppid();

        printf("child pro pid = %d, pre_pid = %d \n",pid, pre_pid);

 

        sleep(10);

        exit(10);

        

        break;

 

 

    }

 

}

int main(int argc, char *argv[])

{    test15();

    return 0;

}

在 Solrais 下用 CC 编译

CC Test.cpp -o Test

之后运行结果为

bash-2.03$ Test

pro p_malloc = 305952

set pid

set pid,pid = 23881

child pro pid = 23881, pre_pid = 22819

set pid

set pid,pid = 0

free(p_malloc) , p_malloc = 305952,pid = 0

 

我认为说明了几个问题:

1:从父进程吧数据拷贝到子进程中的时候,不按照拷贝构造函数的方式来拷贝,直接进行字节的拷贝。

2:对于指针的拷贝,只拷贝指针的值,不拷贝指针所指向的内容(malloc 和 new 方式申请的内存)。

另外,当进程以 return 的方式结束的时候,对于没有释放对象,系统会调用将其释放并调用他们的析构函数,如果以 exit 方式退出则直接释放内存不调用析构函数。有点类似于 delete 和 free 的区别。

再回到我们上面所谈的地方,此时如果有一个连接在一个类的构造函数中实现,断开连接在它的析构函数中实现。如果这个类在 fork 之前已经 new 了出来,那么当父进程以return的方式结束的时候,它的析构函数就会断开连接,所以子进程中也无法使用该连接了。这里只是比较随意的谈到了一个例子,可能还会有更多的问题因这个原因而产生,所以在使用 fork 的时候我们要想到这一点从而尽可能的避免 BUG 的产生。

 

posted on 2008-12-23 14:22 yang-chunlei 阅读(2407) 评论(4)  编辑 收藏 引用

评论

# re: 使用 fork 所要注意的 2008-12-23 17:41 lymons
>>>>2:对于指针的拷贝,只拷贝指针的值,不拷贝指针所指向的内容(malloc 和 new 方式申请的内存)。

对于这点我不认同。
创建出来的子进程是要完全拷贝父进程的内存地址空间(内存印像),也就是子进程的内存和父进程的是一模一样的,当然也包括在堆(heap)里存放的动态内存。
所以,除了指针之外,指针指向的内容也是要拷贝出来。
虽然你打印出来的p_malloc_指向的地址都是相同的,但这个地址是存在于在两个不同的虚拟地址空间,而且内容是一模一样的,但它们是两块不同的物理内存。所以打印出相同的地址,这并不能说明父子进程的指针是指向同一块内存的。

另外,在子进程里对p_malloc_进行任意的读写都不会影响到父进程的这块同样的内存,反之,父进程来操作地址相同的内存也不会影响到子进程。

你可以在子进程的代码里,在sleep(10);之后添加对p_malloc_进行读写的语句,看它能否正确执行。请你验证一下。  回复  更多评论
  

# re: 使用 fork 所要注意的 2008-12-23 20:29 xiaochong
fork后的内存肯定要复制的,不过是在写时复制   回复  更多评论
  

# re: 使用 fork 所要注意的 2008-12-24 10:26 yang-chunlei
@lymons
我测试过了,确实如你所说,当初我没有想到每个进程都有自己的虚拟空间。但是在我所举的建立连接的那个例子中,父进程把连接断开了之后,服务器也随之断开了这个连接,虽然子进程中的数据没有变,但是服务器的链接状态改变了,所以子进程再去进行数据传输则会失败的。还有个问题,如果父进程没有没有结束,链接也没有断开,那么当服务器发送数据过来的时候,该哪个进程去接收呢,因为两个进程内存中的数据和链接状态都是一样的,这样岂不是造成了一个端口被两个进程监听的问题。这一点我还没有去测试,稍后有了答案再进行回复。  回复  更多评论
  

# re: 使用 fork 所要注意的 2008-12-29 22:44 皇家救星
对于LS所说的情况,我看有些书是推荐子进程生程后立即关闭从父进程继承的监听用的socket  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理