linux 环境编程学习笔记 第15天 进程


寒假学习 第15天 (linux 高级编程)  笔记 总结


接着昨天

一、进程

2.创建进程

   (1) int system(const char *command);

   (2) FILE *popen(const char *command, const char *type);

   (3) exec系列函数

       int execl( const char *path, const char *arg, ...);

       //第一个参数:替换的进程,第二个参数..... 命令行

       //命令行格式:命令名   选项参数     并且命令行必须、以0结尾
       int execlp( const char *file, const char *arg, ...);
       int execle( const char *path, const char *arg , ..., char * const envp[]);
       int execv( const char *path, char *const argv[]);
       int execvp( const char *file, char *const argv[]);

       替换当前进程的代码空间的代码数据,函数本生不会创建进程 见例子1

  execl与execlp的区别

     execl必须是绝对路径,execlp不用 见例子2


例子1:

test.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    printf("%d\n",getpid());
    sleep(5);
    return 0;
}

 

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    printf("%d\n",getpid());
    int r=execl("test","mytest",NULL);//结尾必须是0
    printf("end %d\n",r);
    return 0;
}

 

运行时 printf(“end %d\n”,r); 不会执行

并且打印的两个pid相同


例子2:

 

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    printf("%d\n",getpid());
    int r=execl("/bin/ls","ls","-l",NULL);
    printf("end %d\n",r);
    return 0;
}

 


(4) pid_t fork(void);

  1. 克隆父进程的代码跟执行位置,重fork开始就分两个进程进行,例子1

 

  2. fork创建的进程,父子进程是同时进行的

      子进程创建好了以后,父进程马上把时间片交给系统,然后系统在调度下一个时间片由那个执行。 例子2

 

例子1:

 

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    printf("before creat process!\n");
    int pid=fork();
    printf("after creat process:%d\n",pid);
    return 0;
}

结果:
before creat process!
after creat process:2651
after creat process:0     //为什么为0?因为fork不光克隆父进程的代码,还克隆了父进程的执行位置,所以子进程中pid=fork() 是没有执行的,pid为0.


例子2:

 

 

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    printf("before creat process!\n");
    int pid=fork();
    while(1)
    {

        if(pid==0){
            printf("AAAAA\n");
            sleep(1);
        }else{
            printf("BBBBB\n");
            sleep(1);
        }
    }
    return 0;
}

结果:AAAAA 跟BBBBB交叉出现

 

 

3.进程的应用

 

 

  1. fork 的用处? 使用fork实现多任务(Unix本身是不支持多线程,有时就要用fork创建多进程)

  2. 实现多任务的方式

          1.线程

          2.进程

          3.信号

          4.异步

          5.进程池与线程池

例子:使用进程创建多任务(让屏幕同时显示时间跟随机数)

#include <curses.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>

WINDOW *wtime,*wnumb;
int main(int argc, const char *argv[])
{
    initscr();
    box(stdscr,0,0);

    wtime=derwin(stdscr,3,10,0,(COLS-10));   //右上角显示时间
    wnumb=derwin(stdscr,3,11,(LINES-3)/2,(COLS-11)/2); //中间显示随机数

    box(wtime,0,0);
    box(wnumb,0,0);

    refresh();
    wrefresh(wtime);
    wrefresh(wnumb);

    if(fork()){     //父进程
        time_t tt;
        struct tm *t;
        while(1)
        {

            tt=time(0);
            t=localtime(&tt);
            mvwprintw(wtime,1,1,"%02d:%02d:%02d",t->tm_hour,t->tm_min,t->tm_sec);
            refresh();
            wrefresh(wtime);
            wrefresh(wnumb);
            sleep(1);

        }
    }else{   //子进程
        while(1){
            mvwprintw(wnumb,1,1,"%8d",rand()%100000000);
            refresh();
            wrefresh(wtime);
            wrefresh(wnumb);
            usleep(100000);

        }
    }

    endwin();
    return 0;
}

 

4.理解进程

(1).父子进程的关系

       是独立的两个进程,有各自的pid

       从进程树可以看出两个独立但不平行是父子节点关系(pstree可以查看进程树)

(2)  父进程先结束

       子进程就依托根进程init (孤儿进程)

       理论上孤儿进程对系统没有任何的危害。

    子进程先结束

        子进程会成为僵尸进程。

        僵尸进程不占用内存,CPU但在我们的进程任务管理树上占用一个节点。

        僵尸进程会造成进程名额资源的浪费,所以要处理僵尸进程

 (3). 僵尸进程的回收(使用wait回收)

例子:

 

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, const char *argv[])
{
    if(fork()){
        int status;    
        printf("parent\n");
        wait(&status);
        printf("%d\n",WEXITSTATUS(status));
        sleep(1000);
    }else{
        printf("child\n");
        sleep(10);
        exit(34);
    }
    return 0;
}

运行10秒后 子进程回收,不会变成僵尸进程,pstree查看看不到子进程

 

 

4. 父进程怎么知道子进程退出

     子进程结束通常会向父进程发送   一个SIGCHLD信号(kill -l 可以查看所有信号)

5.父进程处理子进程退出信号

      signal(int sig,void(*fun)(int));

      向系统注册:告诉系统只要sig信号发生,系统就停止进程,并调用fun函数。

      当函数执行完毕,继续原来的进程         (中断)

          1 实现处理函数

          2使用signal来绑定信号与函数

例子:僵尸进程回收

 

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

void deal(int s)
{
    int status;
    wait(&status);
    printf("回收中!...\n");  //这个函数不执行完毕程序就不会进行下去
    sleep(10);                   
    printf("回收完毕:%d\n",WEXITSTATUS(status));
}

int main(int argc, const char *argv[])
{
    if(fork()){
        signal(17,deal); //向系统注册  SIGCHLD 就是 17
        while(1)
        {
            printf("parent\n");
            sleep(1);
        }
    }else{
        printf("child\n");
        sleep(10);
        printf("child out\n");
        exit(34);
    }
    return 0;
}

 

 

6. 父子进程的资源访问

   (1).内存资源

1
#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    int a = 20;
    if(fork()){
        printf("patent a:%d\n",a);
        printf("patent a:%p\n",&a);
        a=33;
        sleep(3);
    }else{
        printf("child a:%d\n",a);
        printf("child a:%p\n",&a);

        sleep(2);

        printf("child a:%d\n",a);
        printf("child a:%p\n",&a);
    }
    return 0;
}


结果:
patent a:20
patent a:0x7fffbd3e533c
child a:20
child a:0x7fffbd3e533c
child a:20
child a:0x7fffbd3e533c
 
#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    int *a = (int *)malloc(4);
    *a = 20;
    if(fork()){
        printf("patent a:%d\n",*a);
        printf("patent a:%p\n",a);
        a=33;
        sleep(3);
    }else{
        printf("child a:%d\n",*a);
        printf("child a:%p\n",a);

        sleep(2);

        printf("child a:%d\n",*a);
        printf("child a:%p\n",a);
    }
    return 0;
}


结果:
patent a:20
patent a:0x1d2f010
child a:20
child a:0x1d2f010
child a:20
child a:0x1d2f010

由上面两例子可得到
子进程克隆了整个内存区域,但内存区域指向不同的物理空间(虚拟地址相同,但映射不同)
尽管克隆,但内存独立,不能互相访问。
 
多进程实现多任务,进程之间的数据交换是个大问题
Inter-Process Commucation 
 
 
2
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>

int main(int argc, const char *argv[])
{
    int *a = (int *)mmap(0,4,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_SHARED,0,0);
    *a = 20;
    if(fork()){
        printf("patent a:%d\n",*a);
        printf("patent a:%p\n",a);
        *a = 33;
        sleep(3);
    }else{
        printf("child a:%d\n",*a);
        printf("child a:%p\n",a);

        sleep(2);

        printf("child a:%d\n",*a);
        printf("child a:%p\n",a);
    }
    return 0;
}

结果:
patent a:20
patent a:0x7f76f73f1000
child a:20
child a:0x7f76f73f1000
child a:33              //改变了,
child a:0x7f76f73f1000

#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>

int main(int argc, const char *argv[])
{  //跟上面的例子的区别只是把PROT_SHARE 改成MAP_PRIVATE
    int *a = (int *)mmap(0,4,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_PRIVATE,0,0);
    *a = 20;
    if(fork()){
        printf("patent a:%d\n",*a);
        printf("patent a:%p\n",a);
        *a = 33;
        sleep(3);
    }else{
        printf("child a:%d\n",*a);
        printf("child a:%p\n",a);

        sleep(2);

        printf("child a:%d\n",*a);
        printf("child a:%p\n",a);
    }
    return 0;
}
结果:
patent a:20
patent a:0x7fe9bb8ab000
child a:20
child a:0x7fe9bb8ab000
child a:20   //没有改变
child a:0x7fe9bb8ab000
映射内存
        MAP_SHARED: 映射到同一物理内存
        MAP_PRIVATE: 映射到不同的物理内存
 
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>

int main(int argc, const char *argv[])
{
    int *a=sbrk(4);
    *a = 20;
    if(fork()){
        printf("patent a:%d\n",*a);
        printf("patent a:%p\n",a);
        *a = 33;
        sleep(3);
    }else{
        printf("child a:%d\n",*a);
        printf("child a:%p\n",a);

        sleep(2);

        printf("child a:%d\n",*a);
        printf("child a:%p\n",a);
    }
    return 0;
}
结果:
patent a:20
patent a:0x12a1000
child a:20
child a:0x12a1000
child a:20
child a:0x12a1000

sbrk映射的是MAP_PRIVATE的

   (2).文件资源

#include <stdio.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
    int fd=open("test.txt",O_RDWR);
    if(fork()){
        printf("patent\n");
        char buf[1024]={0};
        read(fd,buf,1024);
        printf("%s\n",buf);
    }else{
        printf("child\n");
        char buf[1024]={0};
        read(fd,buf,1024);
        printf("%s\n",buf);

    }
    close(fd);
    return 0;
}

test.txt 内容
123456

结果:
patent
123456

child


文件是独立的,close() ,每个进程都要有
 
为什么子进程没有内容?
因为read中系统管理的指针移动了,两个进程操作的是同一个内核对象
 
改成下面这样就没问题了
#include <stdio.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
    int fd=open("test.txt",O_RDWR);
    if(fork()){
        printf("patent\n");
        char buf[1024]={0};
        lseek(fd,0,SEEK_SET);
        read(fd,buf,1024);
        printf("%s\n",buf);
    }else{
        printf("child\n");
        char buf[1024]={0};
        lseek(fd,0,SEEK_SET);
        read(fd,buf,1024);
        printf("%s\n",buf);

    }
    close(fd);
    return 0;
}
结果:
patent
123456

child
123456
 
 
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

main()
{
    int fd=open("test.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
    if(fork())
    {
        write(fd,"Hello",5);
        close(fd);
    }else{
        write(fd,"Word",5);
        close(fd);
    }
}

cat test.txt结果:
HelloWord
 
 
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

main()
{
    if(fork())
    {
        int fd=open("test.txt",O_RDWR);
        write(fd,"Hello",5);
        close(fd);
    }else{
        int fd=open("test.txt",O_RDWR);
        write(fd,"Word",5);
        close(fd);
    }
}

cat test.txt 结果:
Word
 

结论:
   两个进程之间,文件描述付指向的是同一个文件对象
    进程的数据交换,居于两种方式:
             内存:有序/无序    mmap
             文件:有序/无序    普通文件
     由此可得出所有进程对象的通信都是基于内核对象的:文件,内存,队列