您的位置:首页 > 运维架构 > Linux

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

2014-08-23 11:36 281 查看
————————————进程————————————————

   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));// 初始化buf
stream = 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(私有):映射到不同的物理内存。



    
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息