fork父子进程笔记

创建进程

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	pid_t id;
	
	id = fork();
	
	int i = 0;
	if(id < 0)
	{
		perror("fork");
		exit(1);
	}
    else if(id == 0)
	{
		for(i = 0; i < 10; i++)
		{
		  printf("i am child,my pid = %d   my parent pid = %d\n",getpid(),getppid());
		  sleep(1);
		}
		
	}
	else
	{
		for(i = 0; i < 10; i++)
		{
		  printf("i am parent, my pid = %d\n",getpid());
		  sleep(1);
		}

	}
}

子进程的pid为3932   父进程的pid为3931

 

孤儿进程

父进程中止后,子进程任然进行,此时子进程将被init进程收养。

 

进程的资源

 

fork函数时调用一次,返回两次。在父进程和子进程中各调用一次。子进程中返回值为0,父进程中返回值为子进程的PID。程序员可以根据返回值的不同让父进程和子进程执行不同的代码。子进程是父进程的副本,获得了父进程数据空间、堆和栈的副本;父子进程并不共享这些存储空间,共享正文段(即代码段);因此子进程对变量的所做的改变并不会影响父进程。一般来说,fork之后父、子进程执行顺序是不确定的,这取决于内核调度算法。进程之间实现同步需要进行进程通信。
 

#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>

int g_iNum=10; //全局变量 静态存储区
int main()
{
    char *p = (char *)malloc(20); //堆 

    strcpy(p,"world");

    int stack_num = 100; //栈  局部变量
    pid_t pid;
    pid=fork();

    if(0==pid)
    {//这里是子进程的代码
        printf("----------------------------------------------------------------\n");
        printf("I am child process,g_iNum=%d\n",g_iNum);
        printf("I am child process,%s\n",p);
        printf("I am child process,stack_num =%d\n",stack_num);
        strcpy(p,"hello");
        g_iNum=5; 
        stack_num = 200;
        printf("----------------------------------------------------------------\n");
        printf("I am child process,g_iNum=%d,g_iNum_addr=%p\n",g_iNum,&g_iNum);
        printf("I am child process,%s,p_addr = %p\n",p,p);
        printf("I am child process,stack_num = %d,stack_num_addr = %p\n",stack_num,&stack_num);
    }else{
     //这里是父进程的代码
        printf("----------------------------------------------------------------\n");
        printf("I am parent process,g_iNum=%d\n,g_iNum_addr",g_iNum,&g_iNum);
        printf("I am parent process,%s,p_addr = %p\n",p,p);
        printf("I am parent process,stack_num = %d\n",stack_num);
        sleep(1);
        printf("----------------------------------------------------------------\n");
        printf("I am parent process,g_iNum=%d,g_iNum_addr=%p\n",g_iNum,&g_iNum);
        printf("I am parent process,%s,%p\n",p,p);
        printf("I am parent process stack_num = %d,stak_num_addr = %p\n",stack_num,&stack_num);
        return 0;
    }
}

可以看到,其值发生了改变,这也说明,父子进程的堆区数据、栈区数据、全局数据是不共享的。

但是所指向的虚拟地址是一样的,也就是说不同的进程的变量却有相同的虚拟地址(注:程序中变量的地址都是虚拟地址,而非物理地址)。原因是内核会为每个进程分配4G的虚拟地址空间,这4G的虚拟地址空间地址分布都是一样的,由于子进程虚拟地址空间的数据都是从父进程中拷贝而来的,都是一样的,因此相同的数据在4G的虚拟地址空间中的分布也是一样的。那为什么相同的虚拟地址(变量地址)最终会得到不同的变量值呢?原因是虽然虚拟地址分布是一样的,但是由于相同的虚拟地址映射到不同的物理地址,所以我们才会得到不同的变量值。

 数据传输

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char* argv[])
{
	pid_t pid;
	
	int fd;
	
	char *info = "hello world";
	
	char buf[64] = {0};
	
	int nBytes;
	
	fd = open("file",O_RDWR|O_CREAT,0644);
	
	if(fd < 0)
	{
		perror("open file failed");
		exit(1);
	}
	printf("before fork\n");
	
	if((pid = fork()) < 0)
	{
		perror("fork error");
	}
    else if(pid == 0)
	{
		
		if((nBytes = write(fd,info,strlen(info)))<0)
		{
			perror("write failed");
		}
		exit(0);
	}
	else
	{
		sleep(1);
		
		lseek(fd,0,SEEK_SET);
		
		if((nBytes = read(fd,buf,64)) < 0)
		{
			perror("read failed");
		}
		printf("%s\n",buf);
		
	}
    exit(0);

}

 

父进程首先打开了一个文件file。当调用fork创建紫进程后,子进程继承父进程中打开的文件,也就是说父、子进程共享该文件。

 

linux会为进程打开三个文件,标准输入文件、标准输出文件、标准错误文件。

子进程会继承父进程打开的文件

 

两种典型的用法

一个父进程希望复制自己,使父,子进程同时执行不同的代码段。这在网络服务进程中常见--父进程等待委托者的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求,父进程则继续等待下一个服务请求。

一个进程要执行不同的程序。 这对Shell是常见的情况。在这种情况下,子进程在从fork返回后立即调用execve来执行新程序。

 

后记:

【1】进程的基础
 1)进程与程序
    程序:保存在磁盘上的一组指令的有序集合。静态的,没有任何的执行的概念。
    进程:是程序动态运行的一次执行过程。动态的,具有一定的生命周期,包括创建,调度,消亡。
 2)进程由2部分组成
    用户空间资源:数据段,代码段,堆栈段,BSS段,进程资源,资源自动释放
    内核空间资源:进程标识符,PCB(进程控制块),进程的属性信息,不会主动的释放资源,要需要人为主动回收。
 3)主要的进程标识
  PID 进程PID
  PPID 父进程PID
  注意:PID是一个唯一的,正整数。
 4)进程的执行过程
 创建:每启动(运行)一个进程,内核就会为当前的进程分配内存空间,保存变量,代码等。
 调度:CPU的(优先级)调度,上下文的切换(用户空间和内核空间的切换)。
 消亡:进程结束,需要回收资源,主动回收(调用相关的函数接口)。
 5)Linux中的进程包含三个段
 数据段
 代码段
 堆栈段
 PCB(进程控制块)
 6)进程的种类:
    交互进程:
    该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行,可以与用户直接交互,
    且受终端的控制。
    前台进程-----------受终端的控制
    后台进程------------不受终端控制
    注意:将前台进程变成后台进程 ./a.out &
    批处理进程:
    该是一个进程序列,负责按照顺序启动,该类进程不属于某个终端,不与用户进行交互,在后台运行,称之为进程的集合。
    守护进程:
    长期运行在后台做某些服务,一般在Linux启动时开始运行,系统结束时停止运行。
    本身属于后台进程, 不受终端控制。自己做为会话组组长。

【2】进程的相关命令
  top 动态查看进程的属性信息(PR,NI优先级)
  ps 
  px ajx  查看进程的属性信息
  ps aux  查看进程的属性信息
  特殊:0号进程:内核进程
        1号进程:init进程
        每一个进程都有父进程,除了0号进程(理解用2叉树理解)
  ps  axj 指令:
  ps aux | grep 可执行程序的名称
  ps aux/-aux | grep a.out
        PPID       PID   PGID      SID   TTY      TPGID        STAT (***)    UID     TIME   COMMAND
        0          1      1        1     ?           -1         Ss       0     0:01    /sbin/init
        父亲进程id 进程号 进程组id 会话id 终端     终端进程组id 状态    用户id   时间   进程命程
       
       int getdtablesize(void);   // 得到最大能打开文件描述符个数
       Here are the different values that the s, stat and state output specifiers (header "STAT" or "S") will
       display to describe the state of a process:
       D    uninterruptible sleep (usually IO)  // 不可中断睡眠态
       R    running or runnable (on run queue)  // 运行态(正在运行,等待运行)
       S    interruptible sleep (waiting for an event to complete)  // 睡眠态
       T    stopped, either by a job control signal or because it is being traced.  // 停止态
       X    dead (should never be seen)  // 死亡态,瞬间发生,程序员不可见
       Z    defunct ("zombie") process, terminated but not reaped by its parent.  // 僵尸态

       For BSD formats and when the stat keyword is used, additional characters may be displayed:
       <    high-priority (not nice to other users)  // 高优先级进程
       N    low-priority (nice to other users)  // 低优先级进程
       L    has pages locked into memory (for real-time and custom IO) 
       s    is a session leader  // 会话组组长
       注意:进程---》进程组组长---》会话组组长
       l    is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)  // 进程中包含了线程
       +    is in the foreground process group.  // 前台进程
  kill
  1)kill -l  查看信号
       SIGINT       //默认属性结束进程  ctrl+c
       SIGQUIT      //默认属性结束进程  ctrl+ \
       SIGKILL      // 杀死进程
       SIGUSR1      // 用户自定义的信号,默认结束进程
       SIGUSR2      // 用户自定义的信号,默认结束进程
       SIGCHLD      // 儿子进程死亡,系统会给父亲进程发送 SIGCHLD 信号
       SIGALRM      // 闹钟信号,默认结束进程
       SIGSTOP      // 停止信号, 进程进入暂停态  ctrl + z
       SIGTSTP      // 停止信号
       SIGCONT      // 恢复运行信号
  注意:SIGKILL,SIGSTOP不能被捕捉,忽略,屏蔽。
  2)kill 向指定的进程发送信号
  kill +信号的名称(通过kill -l查看得到的信号)+PID 向执行的进程发送信号。
  注意:只有 kill -9 + pid 是具有杀死进程的功能,其他的数字没有。 
  bg  将挂起的进程放到后台执行(暂停进程)
  ./aout &  将普通执行的程序放到后台执行
  fg  将后台进程放到前台执行
  注意:bg 1
        bg 2
        fg 1
        fg 2
        ...
  nice 在进程启动前改变进程的优先级,并将进程运行起来
  sudo nice -n (优先级)(可正可负)+ ./a.out
  sudo nice -n -10 ./3
  renice 在进程运行过程中改变进程的优先级
  sudo renice -n +(优先级数)+pid
  sudo renice -n -15 pid
  ps -le
 
【3】进程的相关系统调用
    源进程与新创建进程的关系:子进程精确复制父进程的代码,复制了代码段,数据段,堆栈段,BSS段,文件
    描述符,缓冲区空间;空间各自独立,PID,PPID,ino号,进程PCB控制块没有复制。
    1)进程的创建---fork
     #include <unistd.h>
        pid_t fork(void);
    函数的功能:创建新的子进程(在已有的进程的基础上)
    参数:无
    返回值:pid < 0 出错 没有创建子进程
            pid =0 执行子进程
            pid >0 执行父进程
    注意:孩子进程从fork之后执行,因为在fork之后才出现父子进程。
          父子进程的执行顺序不确定,谁先抢占到CPU谁先执行。
          谁先执行并不代表谁先执行完。
          父子进程没有分开执行
          父子进程分开执行
    2)getpid
     #include <sys/types.h>
     #include <unistd.h>
     pid_t getpid(void);
    函数的功能:获取当前进程的PID
    参数:无
    返回值:当前进程的PID
    getppid
     pid_t getppid(void);
    函数的功能:获取当前进程的父进程PID
    参数:无
    返回值:当前进程的父进程PID
    3)子进程对父进程缓冲区的处理
    结论:创建父子进程,子进程精确复制父进程,复制包括数据段,代码段,BSS段,堆空间,栈空间,文件描述符(特例),     用户空间的缓冲区,当子进程复制父进程的缓冲后,随着子进程结束,会刷新缓冲区当中的数据,所以造成在fork之前的打印语句会执行2次。
    注意:子进程是在fork语句之后执行的。
     子进程对父进程打开的文件描述符的处理
     注意:虽然子进程复制用户空间父进程的文件描述符,但是在内核空间对应的文件管理表项中的文件指针是共享的,====》共享偏移量
    4)僵尸进程和孤儿进程
      1>僵尸进程是如何产生?
      父进程活着,儿子进程死了,但是父进程并没有为儿子进程回收尸体,从而产生僵尸进程
      2>如何避免僵尸进程?
       1)形成孤儿进程---解决僵尸进程。
       2)父进程活着,儿子进程死了,让父亲进程主动调用回收函数去回收儿子进程的资源(wait,waitpid)
      3>孤儿进程是如何产生的?
      当父亲进程优先于子进程结束,子进程被init(1号进程)收养,此时将这个进程称之为孤儿进程。
      注意:孤儿进程的产生时一个正常的现象。
    5)进程的退出
    exit----结束一个进程,会刷新缓冲区
    _exit-----结束一个进程,不会刷新缓冲区
    注意:如果程序正常结束也会刷新缓冲区

    exit/_exit与return的区别
    1)return 结束一个函数体,函数体结束,进程不一定结束
    2)exit/_exit结束一个进程,进程结束,函数体一定结束。
    6)wait/waitpid
    wait----回收任意一个子进程的资源


    #include <sys/types.h>
        #include <sys/wait.h>
    pid_t wait(int *status);
    函数的功能:阻塞等待回收子进程的资源
    参数:status 进程结束时的状态信息
    返回值:成功返回回收子进程的pid
            失败-1
    waitpid---自己指定回收某一个子进程的资源。
    pid_t waitpid(pid_t pid, int *status, int options);
    函数的功能:回收特定子进程的资源
    参数:pid 要回收子进的pid
          pid <-1 回收当前调用进程|pid|等于同组pid的任意子进程
          pid =-1 回收任意一个子进程的资源 ==wait
          pid =0 回收同组进程下的任意一个子进程
          pid >0 回收指定子进程的pid
          status 子进程结束时的状态
          options   0   阻塞
                   WNOHANG 非阻塞       
    返回值:成功返回子进程pid 
            WNOHNAG 没有子进程结束 0
            失败-1;
    7)阻塞与非阻塞(银行办业务)
    阻塞:一直等待条件的发生,直到得到结果,保证了结果。
    非阻塞:等待条件的发生,但是不一定等待到结果,保证了时间。
    8)exec族函数---在子进程当中启动新的程序
     execl
       #include <unistd.h>
       int execl(const char *path, const char *arg, ...);
       函数的功能:在子进程中运行新的程序代码
       参数:path 要执行程序的路径+名称 
       注意:path必须是一个绝对路径
     execlp
     int execlp(const char *file, const char *arg, ...);
       函数的功能:在子进程中运行新的程序代码
       参数:path 要执行程序的路径+名称
       注意:文件的名称可以不用写绝对路径,可以在当前的环境变量下去寻找。
     execle
     int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
     函数的功能:在子进程中运行新的程序代码
     注意:path必须是绝对路径,最后要以一个新的指针数组结束。
     execv
     int execv(const char *path, char *const argv[]);
       函数的功能:在子进程中运行新的程序代码
       注意:path是一个绝对路径
             只有2个参数,没有可变参数
             结尾以指针数组结束
       先定义一个指针数据,将要运行的命令写入指针数组,然后将指针数组的名称传入
     system
     #include <stdlib.h>
     int system(const char *command);
    函数的功能:在当前运行的进程当中启动新的程序
    参数:command 命令
    返回值:成功返回非负数
            失败-1
    9)守护进程
        守护进程的定义:守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,
       通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件
       守护进程常常在系统启动时开始运行,在系统关闭时终止
       Linux系统有很多守护进程,大多数服务都是用守护进程实现的
        守护进程创建的步骤:
        1,创建子进程,父进程退出
           fork
        2,在子进程中创建新会话 ,并且成为会话组组长
          setsid
        3,改变当前目录为根目录
          chdir
        4,重设文件权限掩码 
           umask---放开权限
        5,关闭文件描述符 
            getdtablesize
        6,做服务
            write
            read
        
          

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux中的fork()函数可以创建一个新的进程,这个新进程是原进程的副本,也就是说,它们有相同的代码、数据和堆栈。fork()函数会返回两次,一次是在进程中返回子进程进程ID,另一次是在子进程中返回。进程子进程之间的区别在于它们的进程ID不同,以及它们的进程ID也不同。进程子进程共享文件描述符、信号处理程序和文件锁等资源,但是它们各自拥有自己的地址空间和堆栈。 ### 回答2: Linux中fork()是创建进程的一个系统调用,fork()会复制一份进程的全部资源,包括代码段、数据段、堆栈、打开的文件、进程组信息等都会被复制到子进程中。因此,原来进程有的资源,在子进程中都会有一个副本。fork()调用成功后,会返回两次,一次是在进程中返回子进程的PID,另一次是在子进程中返回0。 在fork()调用完成后,就会出现两个进程进程子进程进程中的所有资源都被完全复制到了子进程中,但子进程具有独立的内存空间和进程ID,因此进程子进程之间的内存空间是互相独立的。在这种情况下,进程子进程执行的代码相同,但是子进程是一个全新的进程,具有自己的内存空间和进程上下文。 如果子进程想和进程之间进行通信,可以使用管道或者共享内存。透过管道或共享内存,子进程可以读取进程中的数据,或者将自己产生的数据发送给进程。另外,还可以使用信号或者消息队列进行通信。 需要特别注意的是,fork()调用成功后,子进程中会复制进程中的所有资源,包括打开的文件描述符等。因此,子进程需要关闭不需要的文件描述符,避免浪费系统资源。此外,子进程也需要确保在调用exec()函数之前,所有需要使用的文件描述符都已经打开了,否则在子进程中打开的文件描述符可能会覆盖进程中已经打开的文件描述符,导致出现错误。 ### 回答3: 在Linux系统中,每个进程都有一个唯一的进程ID(PID),以及一些其他的属性和信息。除了在启动时由init进程创建的特殊进程以外,每个进程都是由另一个进程fork”出来的,即在原有进程的基础上创建一个全新的进程。 在C语言中,可以使用fork()函数来实现这个操作。每次调用fork()函数时,会创建出一个全新的进程,称为“子进程”,并且这个子进程就是由“进程fork出来的。进程子进程在大部分方面都是相同的,例如二者运行相同的程序、拥有相同的内存空间和变量等等,但在某些方面也有一些差异,例如二者的进程ID不同(进程进程ID就是调用fork()函数前的进程ID,而子进程进程ID是新分配的),以及二者对共享资源的访问方式不同(具体取决于程序的实现方式)。 通常情况下,fork()函数的返回值为0,表示子进程;或者返回一个大于0的数值,表示进程,并且这个数值就是子进程进程ID。如果fork()函数返回一个负数,则表示创建子进程失败。 在实际编程中,可以利用fork()函数来实现一些复杂的应用,例如多进程并行计算、进程间通信等等。此外,fork()函数也是Unix/Linux系统中一种重要的机制,可以实现进程的动态创建和销毁,从而增强了系统的灵活性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值