进程(1)—— 基础理论及fork()

来源:互联网 发布:java获取文件修改时间 编辑:程序博客网 时间:2024/03/29 18:08

一 进程基础理论

  进程 - 一个在内存中运行的程序
  主流的操作系统基本都支持多进程。
  Linux中查看进程的方法
    ps - 查看本终端启动的进程
    ps -aux Linux专用的查看进程
    ps -ef Unix/Linux通用的查看进程
  显示的东西有所差别,Unix系统不直接支持ps -aux ,但/usr/ucb/ps -aux 可以执行。
  
  kill -9 进程ID 可以杀进程。

  如果进程a启动了进程b,a叫父进程,b叫子进程。
  Unix/Linux的进程启动次序,首先内核启动0进程,0进程 启动 1进程和2进程(有些Linux只启动1进程),1进程 和 2进程 再 启动其他所有进程。

  进程的常见状态:
    S - 休眠状态(省资源)
    s - 有子进程
    O - 可运行状态
    R - 运行状态
    Z - 僵尸进程(已经结束但资源没有回收的进程)

父子进程之间的关系:
1. 父进程启动子进程后,父子进程同时运行。
2. 如果子进程先结束,子进程会给父进程发信号,由父进程负责回收子进程的相关资源。
3. 如果父进程先结束,子进程变成孤儿进程,孤儿进程会把init进程(进程1)作为新的父进程,init进程也叫孤儿院。
4. 如果子进程先结束,同时发的信号父进程没有收到或者子进程没有发信号,子进程就变成僵尸进程。

#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main(){    printf("父进程%d开始运行\n",getpid());    pid_t pid = fork();    if(pid == 0){        printf("子进程%d开始运行,父进程%d\n",        getpid(),getppid());        sleep(3);        printf("休眠结束,父进程%d\n",getppid());        exit(0);    }    sleep(1);    printf("父进程%d结束\n",getpid());    return 0;}

二 进程ID

关于进程的ID - PID
进程ID在同一时刻 确保唯一,但支持延迟重用。
取进程ID的函数:
  getpid() - 取当前进程id
  getppid() - 取当前进程的父进程的PID
  getuid() geteuid() - 取有效用户的ID


三 创建子进程函数 - fork()/vfork()

pid_t fork(void); - 非常简单但非常复杂的函数
fork()是通过复制自身(父进程)创建子进程。
fork()创建的子进程,会复制父进程除代码区之外的内存区域,但和父进程共享代码区。
fork

#include <stdio.h>#include <unistd.h>int main(){  printf("begin\n");  pid_t pid = fork();//创建一个子进程  printf("end:%d\n",pid);}

  fork()之前的代码 父进程执行一次,fork()之后的代码父子进程分别执行一次。fork()函数会有两次返回,父进程返回 子进程ID,子进程返回0。因此可以用fork()返回值区分父子进程。返回 -1 代表失败。
if(pid == 0){
//子进程
}else{
//父进程
}

#include <stdio.h>#include <unistd.h>#include <stdlib.h>int main(){  pid_t pid = fork();  if(pid == -1) perror("fork"),exit(-1);  if(pid == 0){//子进程      printf("我是子进程%d,父进程是%d\n",      getpid(),getppid());     //exit(0);//子进程可以使用exit()退出  }else{      printf("我是父进程%d,子进程是%d\n",      getpid(),pid);  }    printf("end\n");    return 0;}

  fork()之后,父子进程同时运行,但谁先运行,谁先结束都不确定。不同的操作系统对于谁先运行算法不同。

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>int i1 = 100;//全局int main(){    int i2 = 100;//栈,子进程的i2是复制得到    char* str = malloc(20);//堆    strcpy(str,"abcd"); pid_t pid = fork();        int i3 = 100;//栈,父子进程分别定义    if(pid == 0){//子进程      i1 = 200; i2 = 200; i3 = 200;int i4 = 200;      strcpy(str,"1234");         printf("child:i1=%d,i2=%d,i3=%d,i4=%d,str=%s,&   i1=%p\n",i1,i2,i3,i4,str,&i1);    exit(0);    }  sleep(1);  printf("father:i1=%d,i2=%d,i3=%d,str=%s,&i1=%p\n",i1,i2,i3,str,&i1);  return 0;}

fork()如果复制文件描述符时,只复制描述符,不复制文件表。

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>int main(){    int fd = open("a.txt",O_RDWR|O_CREAT,0666);    if(fd==-1) perror("open"),exit(-1);    pid_t pid = fork();    if(pid==0){//子进程        printf("child:fd=%d\n",fd);        write(fd,"hello",5);//复制描述符        close(fd); //不复制文件表        exit(0);    }    printf("father:fd=%d\n",fd);    write(fd,"12345",5);    close(fd);}

fork()创建子进程之后,父子进程代码可以同时执行(并行)。

练习:模拟聊天室
启动10个子进程,每个子进程休眠1秒,然后打印进程xxx退出了聊天室。 xxx就是子进程的id。然后子进程就结束了。
效果是 10个子进程同时结束。
注意:子进程一定要写exit()
   ps -aux 查看一下是否有没有杀掉的子进程
   kill -9 进程ID 杀进程

#include <stdio.h>#include <stdlib.h>#include <unistd.h>int main(){    printf("聊天室开始运行\n");    int i;    for(i=0;i<10;i++){        pid_t pid = fork();        if(pid == 0){           sleep(1);           printf("进程%d离开了\n",getpid());           exit(0);      }    }}

四 进程的结束

进程结束的方式:
正常结束:
  main()执行了return
  执行了exit()/_exit()/_Exit()
  最后一个线程结束
非正常结束:
  信号
  被其他线程取消了最后一个线程

exit()/_exit()/_Exit()的区别
1 _exit()和_Exit()本质上是一样的,区别只是前者是uc函数,后者是标C函数。
2 exit() 和 _Exit()区别是 exit()并不是立即退出,在退出前会调用某些函数,只要函数用atexit(函数指针)注册,退出前就会被调用。_Exit()是立即退出,不会做任何额外的事情。
大多数情况下,退出调用exit(int) 即可。

#include <stdio.h>#include <stdlib.h>void fa(){  printf("fa() is call\n");}int main(){    atexit(fa);//exit()之前要调用fa函数    printf("开始退出进程\n");    exit(0); //正常退出,会调用注册过的函数    //_Exit(0); //立即退出,不调用其他函数    printf("进程结束了\n");    return 0;}
1 0