7 --- Linux 学习笔记之--进程

来源:互联网 发布:淘宝5金冠店铺排行 编辑:程序博客网 时间:2024/04/27 20:46


————————————进程————————————————

   1. 什么是进程



      执行的程序: 代码  资源  CPU

         进程有很多数据维护: 进程状态/进程的属性

    所有进程属性采用结构体维护: 树形数据结构

    ps   查看进程常见属性

    top   查看系统进程执行状况

    pstree(ptree)

              kell  向进程发送信号

       kell  -s  信号

       kell -l  显示进程能接受的所有信号

    进程有很多属性: ps 可以查看属性

--------------------------------------------------------------------

$ ps a     -------当前用户的所有的进程
$ ps au --------所有的用户
$man ps
$ top     ---------查看进程的执行状况(利用空格刷新进程,类似 window 的任务管理器,
q 退出)
$ pstree     ----查看进程树 (树状进程)或者 ptree
$ kill -l -----显示进程能接收的所有信号
$ kill -s 9
224(进程号)      ----- 关闭进程


---------------------------------------------------------------------

  2.创建进程

    1. 代码-加载到内存-分配CPU时间片

        代码由独立的程序存在。

    2. 进程有关的创建函数:

                      2.1        #include <stdlib.h>
                                  int system(const char *command);

        建立独立进程,拥有独立的代码空间。

        等待新的进程执行完毕,system 才返回---阻塞     

  案例:

      使用system调用一个程序。

      观察进程ID。

      观察阻塞。


#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/wait.h>main(){int  r;printf("%d\n",getpid());//r = system("./text");//r = system("ls -l");//r = system("gcc text.c -otext");r = system("make");//printf("%d\n",r>>8 & 255)printf("%d\n",WEXITSTATUS(r));}

            结论:

      新的返回值与system 返回值有关系。

说明:      

system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
返回值
=-1:出现错误
=0:调用成功但是没有出现子进程
>0:成功退出的子进程的id

如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值>。 如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为 system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。

-------------------------------------------------------------------------------------------------------

    2.2  子进程: 被创建的进程。

         父进程: 相对被创建的进程。

         popen: 创建子进程。

             在父子进程之间建立管道。

函数说明

       #include <stdio.h>

       FILE *popen(const char *command, const char *type);
       int pclose(FILE *stream);

    popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程这个进程必须由 pclose() 函数关闭,而不是 fclose() 函数。pclose() 函数关闭标准 I/O 流,等待命令执行结束,然后返回 shell 的终止状态。如果 shell 不能被执行,则 pclose() 返回的终止状态与 shell 已执行 exit 一样。
    type参数只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 "r" 则文件指针连接到 command 的标准输出;如果 type 是 "w" 则文件指针连接到 command 的标准输入。
    command 参数是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用-c 标志,shell 将执行这个命令。
    popen 的返回值是个标准 I/O 流,必须由pclose 来终止。前面提到这个流是单向的。所以向这个流写内容相当于写入该命令的标准输入;命令的标准输出和调用popen 的进程相同。与之相反的,从流中读数据相当于读取命令的标准输出;命令的标准输入和调用 popen 的进程相同。
   popen 通过type是r还是w确定command的输入/输出方向r和w是相对command的管道而言的。r表示command从管道中读入,w表示 command通过管道输出到它的stdout,popen返回FIFO管道的文件流指针。pclose则用于使用结束后关闭这个指针。

案例: 
    使用 popen 调用 ls -l ,并且建立一个管道读取输出。



// popen.c#include <sys/types.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <stdio.h>int main(){FILE  *stream;FILE *wstream;char buf[1024];memset(buf,0,sizeof(buf));// 初始化bufstream = popen("ls -l","r"); // 将"ls -l"命令输出wstream = fopen("test_popen.txt","w+");//新建一个可写的文件fread(buf,sizeof(char),sizeof(buf),stream);//将刚刚FILE* stream的数据流读取到buf中fwrite(buf,1,sizeof(buf),wstream);//将buf中的数据写到FILE  *wstream对应的流中,也是写到文件中pclose(stream);fclose(wstream);return 0;}

---------------------------------------------------
$ gcc popen.c -omain
$ ./main
& cat test_popen.txt
------------------------------------------


-------------------------------------------------------------------------------------------
        2.3  exec 系列函数
       #include <unistd.h>

       extern char **environ;

       int execl(const char *path, const char *arg, ...);
       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[]);
       int execvpe(const char *file, char *const argv[],char *const envp[]);

  作用替换当前进程的代码空间中的代码数据。
     函数本身不创建新的进程

  说明

    fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程(子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。linux将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。),也就是这两个进程做完全相同的事。

    在fork后的子进程中使用exec函数族,可以装入和运行其它程序(子进程替换原有进程,和父进程做不同的事)。

    fork创建一个新的进程就产生了一个新的PID,exec启动一个新程序,替换原有的进程,因此这个新的被 exec 执行的进程的PID不会改变(和调用exec的进程的PID一样)。

      int execl(const char *path, const char *arg, ...);
     第一个参数: 替换程序。
     第二个参数……:命令行
          命令格式: 命令名 选线参数
 案例:使用exec执行一个程序:
    体会:
                      是否创建新的进程? ---- 没有创建新的进程。
       execl的参数的命令行格式   ;
       execl与execlp的区别; (execl只能是当前路径,execlp使用系统搜索路径)
       execl替换当前进程代码。

// text.c#include <stdio.h>#include <unistd.h>int main(){printf("%d\n",getpid());sleep(5);return 0;}

////exec.c#include <stdio.h>#include <unistd.h>int main(){int r = execl("test","mytest",NULL);// 这里的test是可执行文件//int r = execlp("ls","ls","-l",NULL);printf("结束%d\n",r);return 0;}

                    
----------------------------------------------
$gcc text.c -otest
$./test
$gcc exec.c -omain   
$./main   ---- 和上面的效果一样的
-----------------------------------------------
               2.4     fork 函数    
            #include <unistd.h>

            pid_t fork(void);
小案例:

///fork.c#include <stdio.h>#include <unistd.h>int main(){printf("创建进程前!\n");int pid = fork();printf("创建进程后:pid = %d\n",pid);return 0;}

运行结果为:


    发现:
printf("创建进程后:pid = %d\n",pid);
 
   这条语句被执行了两次。  
   结论: 
     (1).创建了新进程。
     (2).新进程的代码是什么?克隆了父进程的代码,而且克隆了执行的位置,
       父进程执行的 ,子进程不执行。0是子进程打印的,非0是父进程打印的。。
     (3).父子进程同时执行

  3.应用进程:
     使用fork创建新的进程有什么意义?
      ---使用fork实现多任务.
      1.进程。
      2.线程
      3.信号
      4.异步
      5.进程池与线程池

案例:
   进程的主要作用是实现多任务
   使用进程创建实现多任务。
   1.UI
   2.建立多任务框架
     显示7为随机数,显示当前时间。
   3.分别处理不同的任务


#include <curses.h>#include <unistd.h>#include <time.h>#include <string.h>#include <stdio.h>#include <stdlib.h>#include <math.h>WINDOW *wtime,*wnumb;main(){initscr();wtime = derwin(stdscr,3,10,3,(COLS-20)/2);wnumb = derwin(stdscr,3,10,(LINES-3)/2,(COLS-11)/2);box(wtime,0,0);box(wnumb,0,0);refresh();wrefresh(wtime);wrefresh(wnumb);if(fork()) // parent shot time {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  // child shot number{int num = 0;int i;while(1){// num = rand()%100000000;num = 0;for(i=0;i<7;++i)    {    num =num*10 + rand()%10;    }    mvwprintw(wnumb,1,1,"%06d",num);    sleep(1);    refresh();    wrefresh(wtime);    wrefresh(wnumb);}}endwin();}

截图:


查看进程文件是否相同:
--------------------------------------------------------------------------
$ps a----发现两个 main,两个进程号 5419 5420
$ cd /proc
$ls -d 54*
$ cd 5419
$cd fd
$ ls -l
$cd ../../5420/fd
$ls -l

----------------------------------------------------------------------------------------------





      
  4.理解进程
     1.父子进程的关系
       有两个目录,所以是两个独立的进程。
       互为父子关系

     2.问题:
       2.1 父子进程先结束,子进程怎么办?
           子进程就是依托根进程init: 变成孤儿进程
           孤儿进程没有危害

       2.2 子进程先结束,父进程怎么办?
           子进程先结束,子进程会变成僵尸进程
           僵尸进程不占用内存,cup,但是进程任务树上有一个节点。
           僵尸进程或造成进程名额资源的浪费,所以必须的杀死僵尸进程。

补充:
-------------------------------------------------------------------------------------------------------------------------------------------------------
     一个进程在调用 exit 命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进(Zombie)的数据结构(系统调用 exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。在 Linux 进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装 SIGCHLD 信号处理函数调用 wait 或 waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么 init 进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程
  Linux 系统对运行的进程数量有限制,如果产生过多的僵尸进程占用了可用的进程号,
将会导致新的进程无法生成。这就是僵尸进程对系统的最大危害

---------------------------------------------------------------------------------------------------------------------------------------------------------
    
    3.僵尸进程使用wait回收
    
    4.父进程怎么知道子进程退出?
      子进程结束通常向父进程发送一个信号。 进程编号17
    5.父进程处理子进程退出信号
      signal(int  sig,void(*)(int));
      向系统注册:只要sig信号发送,系统停止进程,并调用wait函数
      当函数执行完毕,继续原来进程。

      5.1 实现处理函数
      5.2使用signal 绑定信号 与 函数
僵尸进程回收模型案例:

// signal.c#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/wait.h>#include <signal.h>void deal(int s){int status;wait(&status);// status保存状态吗printf("回收中.....\n");sleep(5);printf("回收完毕:%d\n",WEXITSTATUS(status));}void main(){if(fork() == 0){printf("child!\n");sleep(4);printf("退出!\n");exit(8);}else{// 只要sig这个信号到来,就会调用这个函数,回收僵尸进程signal(SIGCHLD,deal);while(1){printf("parent!\n");sleep(1);}}}

补充:

对 WIFEXITED 这个宏的说明:
1,WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个
非零值。
(请注意,虽然名字一样,这里的参数 status 并不同于 wait 唯一的参数--指向整数的指针
status,而是那个指针所指向的整数,切记不要搞混了。
)
2.WEXITSTATUS(status) 当 WIFEXITED 返回非零值时,我们可以用这个宏来提取子进程
的返回值
,如果子进程调用 exit(5)退出,WEXITSTATUS(status)就会返回 5;如果子进程调
用 exit(7),WEXITSTATUS(status)就会返回 7。请注意,如果进程不是正常退出的,也就是
说,WIFEXITED 返回 0
,这个值就毫无意义。
--------------------------------
————————————————————
$ man scandir
$ man ps
$ man system
$ man wait
$ man 2 wait
$ man popen
$ man execl
$ man rand
$ man 2 time
$ man 2 wait
$ man signal

--------------------------------------------------
   问题: 
      父进程的全局栈,堆,局部栈,fd 也会克隆吗?
      6 父进程的资源访问
        6.1 内存资源
        6.2 文件资源
     说明: 子进程克隆了父进程的整个内存区域(全局区/局部区,内存),但是内存区域指向不同的物理空间。
     尽管克隆,但是内存独立,不能相互访问。
     映射内存:
         MAP_SHARED(公有): 映射到同一个物理内存。
         MAP_PRIVATE(私有):映射到不同的物理内存。





    

0 0