Linux C 程序 进程控制(17)

进程控制

1.进程概述
现代操作系统的特点在于程序的并行执行。
Linux是一个多用户多任务的操作系统。
ps .pstree 查看进程
进程除了进程id外还有一些其他标识信息,可以通过相应的函数获得。// 这些函数在unistd.h里声明。

2.Linux进程的结构
Linux一个进程由3部分组成:代码段,数据段,堆栈段。
代码段存放可执行代码
数据段存放程序的全局变量,常量,静态变量
堆栈段存放动态分配的内存变量,堆栈中的栈用于函数调用,存放着函数的参数,函数内部定义的局部变量。

3.Linux进程的状态
    1.运行状态R  runnable
    2.可中断等待状态S  sleeping
    3.不可中断等待状态D  uninterruptible sleep
    4.僵死状态Z  zombile
    5.停止状态T traced  or stoped
    
ps axo stat,euid,ruid,tty,tpgid,sess,pgrp,ppid,pid,pcpu,comm
列stat
后缀的意义:
<高优先级进程
N低优先级进程
L内存锁页,即页不可以被换出内存
s 该进程为会话首进程
l 多线程进程
+ 进程位于前台进程组
eg:Ssl 表示 该进程处于可中断等待状态,为会话首进程,而且是一个多线程进程。
ps axo stat,pid

4.进程控制
Linux进程控制包括创建进程,执行新程序,退出进程,以及改变进程优先级等。
Linux提供进程控制的系统调用:
fork:创建一个进程
exit:终止进程
exec: 执行一个应用程序
wait:将父进程挂起,等待子进程终止
getpid:获取当前进程的进程id
nice:改变进程的优先级

5.进程的内存映像
   1.Linux程序转化成进程
   linux的C程序生成分为4阶段:预编译,编译,汇编,链接。
   编译器gcc经过预编译,编译,汇编将源程序文件转化成目标文件。
   若程序中有多个目标文件或者程序中使用了库函数,
   编译器还要将所有的目标文件或所需的库链接起来,最后生成可执行程序。
   当程序执行时:操作系统将可执行程序复制到内存中,程序转化成进程要经过以下几个步骤:
     1.内核将程序读入内存,为程序分配内存空间。
     2.内核为该进程分配进程标识符(PID)和其他所需资源
     3.内核为该进程保存PID及相应的状态,把进程放入运行队列中等待执行。    程序转化成进城后就可以被操作系统的调度程序执行了。
   2.进程的内存映像
        进程在内存中如何存放可执行程序文件,在将程序转化成进程时,操作系统把可执行程序由硬盘复制到内存中。
        Linux下程序映像的一般布局:
        内存的低地址到高地址依次如下:
        1.代码段:即二进制机器代码,只读,可被多个进程共享,如一个进程创建了一个子进程,父子进程共享代码段,此外子进程还获得父进程数据段,堆,栈的复制。
        2.数据段:存储已被初始化的变量,包括全局变量和已经被初始化的静态变量
        3.未被初始化的数据段:存储未被初始化的静态变量,也被称为bss段
        4.堆:用于存放程序运行中动态分配的变量
         5.栈:用于函数的调用,保存函数的返回地址,函数参数,函数内部的局部变量
        
         高地址还存储了命令行参数和环境变量
        
         可执行程序和内存映像的区别:
         可执行程序位于磁盘,内存映像在内存中。
         可执行程序没有堆栈,程序加载到内存才有堆栈
         可执行程序虽然也有未被初始化的数据段,但他并不被存储在位于硬盘的可执行文件中。
         可执行程序是静态不变的。内存映像随着程序的运行变化的。
        


6.进程操作

 1 #include<stdio.h>

 2 #include<sys/types.h>

 3 #include<unistd.h>

 4 #include<stdlib.h>

 5 

 6 int main(void){

 7 

 8         pid_t pid;

 9 

10         printf("process create begin ......\n");

11 

12         pid = fork();

13 

14         switch(pid){

15                 case 0 :

16                         printf("child process id is %d , parent process is %d\n", pid , getppid());

17                         break;

18                 case -1:

19                         printf("process create failed!\n");

20                         break;

21                 default:

22                         printf("child process id is %d , parent process is %d\n",pid , getppid());

23                         break;

24         }

25 

26         exit(0);

27 }

 




fork之后,父进程执行还是子进程先执行先执行是不确定的,取决于内核的调度算法。
操作系统一般让所有的进程拥有同等执行权利。
除非某进程的优先级比其他进程高。
fork创建进程失败时,返回-1,失败的原因:1.父进程拥有子进程的数量超过限制,2.可供使用的内存不足。
子进程会继承父进程的很多属性:用户id,组id,当前工作目录,根目录,打开的文件,创建文件时使用的屏蔽字,信号屏蔽字,上下文环境,共享的存储段,资源限制。
子进程与父进程不同的属性:子进程有自己的进程id,fork返回值不同,父进程返回子进程的id,子进程的则为0,不同的父进程id,子进程的父进程id为创建他的父进程id
                                                    子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述符。子进程不继承父进程设置的文件锁,
                                                    子进程不继承父进程设置的警告,子进程的未决信号集被清空

 1 #include<stdio.h>

 2 #include<sys/types.h>

 3 #include<unistd.h>

 4 

 5 int main(){

 6         pid_t pid ;

 7         char *msg;

 8         int k;

 9 

10         printf("process creat!\n");

11         pid = fork();

12         switch(pid){

13                 case 0 :

14                         msg = "child process is running\n";

15                         k = 3 ;

16                         break;

17                 case -1:

18                         msg = "proicess  failed \n";

19                         break;

20                 default:

21                         msg ="parent process is running\n";

22                         k = 5;

23                         break;

24         }

25 

26         while(k > 0){

27                 puts(msg);

28                 sleep(1);

29                 k--;

30         }

31 

32         exit(0);

33 

34 }

35     output:

36 [fubin@localhost C]$ ./process_2

37 process creat!

38 parent process is running

39 

40 child process is running

41 

42 parent process is running

43 

44 child process is running

45 

46 parent process is running

47 

48 child process is running

49 

50 parent process is running

51 

52 parent process is running

 


                                                
孤儿进程:如果一个子进程的父进程先于子进程结束,子进程就成了一个孤儿进程,它由init收养,成为init的子进程。

 1 #include<stdio.h>

 2 #include<sys/types.h>

 3 #include<unistd.h>

 4 

 5 int main(){

 6         pid_t pid ;

 7 

 8         pid = fork();

 9 

10         switch(pid){

11                 case 0:

12                         while(1){

13                                 printf("a bankground process ,PID :%d\n ,parentID %d\n",getpid(),getppid());

14                                 sleep(3);

15                         }

16                 case -1:

17                         perror("process failed!");

18                         exit(-1);

19                 default:

20                         printf("i am a parent process ,my pid is %d\n",getpid());

21                         exit(0);

22         }

23 

24         return 0;

25 

26 }

27 

28 output:

29 [fubin@localhost C]$ ./process_3

30 i am a parent process ,my pid is 2561

31 [fubin@localhost C]$ a bankground process ,PID :2562

32  ,parentID 1

33 a bankground process ,PID :2562

34  ,parentID 1

35 a bankground process ,PID :2562

36  ,parentID 1

37 ......

 


先执行父进程,为2561,再执行子进程,父进程此时已经结束,显示子进程2562的父进程pid=1,为init进程


vfork函数:
 1.和fork一样,调用一次,返回两次。
 2.使用fork创建一个子进程时,子进程完全复制父进程的资源,这样得到的子进程完全独立于父进程,有良好的并发性。
 使用vfork创建子进程时,不会将父进程的地址空间完全复制给子进程。而是共享父进程的地址空间,
 子进程完全运行在父进程的地址空间上。子进程对该地址的修改父进程完全可见
 3.fork创建子进程时,哪个先执行取决于系统的调度算法,而vfork保证子进程先执行,当他调用exec或者exit后,父进程才可能被调度执行。
 如果调用exec或exit之前子进程依赖父进程的某个行为,会导致死锁。
 
 
fork:完全复制父进程资源,系统开销大,如果父进程创建之后就马上创建子进程,而父进程不需要了,就没有必要用fork复制资源。

 1 #include<stdio.h>

 2 #include<sys/types.h>

 3 #include<unistd.h>

 4 

 5 int globVar = 5 ;

 6 

 7 int main(){

 8         pid_t pid ;

 9         int var =1 , i ;

10         printf("fork is diff with vfork!\n");

11 

12         pid = fork();

13         //pid = vfork();

14         switch(pid){

15                 case 0 :

16                         i = 3;

17                         //判断i是否大于0,然后自减

18                         while(i-- > 0){

19                                 printf("child process is running!\n");

20                                 globVar++;

21                                 var++;

22                                 sleep(1);

23                         }

24                         printf("child 's globVar = %d , var = %d \n",globVar , var);

25                         break;

26                 case -1:

27                         printf("process creat failed!\n");

28                         exit(0);

29                 default:

30                         i = 5;

31                         while(i-- >0){

32                                 printf("parent process is running\n");

33                                 globVar++;

34                                 var++;

35                                 sleep(1);

36                         }

37                         printf("parent 's globVar = %d , var = %d \n",globVar,var);

38                         exit(0);

39         }

40 

41         return 0;

42 

43 }

44 

45 fork  output:

46 fork is diff with vfork!

47 parent process is running

48 child process is running!

49 parent process is running

50 child process is running!

51 parent process is running

52 child process is running!

53 parent process is running

54 child 's globVar = 8 , var = 4

55 parent process is running

56 parent 's globVar = 10 , var = 6

57 

58 两个进程中的globVar和var独立

59 

60 vfork output:

61 fork is diff with vfork!

62 child process is running!

63 child process is running!

64 child process is running!

65 child 's globVar = 8 , var = 4

66 parent process is running

67 parent process is running

68 parent process is running

69 parent process is running

70 parent process is running

71 parent 's globVar = 13 , var = 5

72 ------------------------------

73 fork is diff with vfork!

74 child process is running!

75 globVar=6,var=2

76 child process is running!

77 globVar=7,var=3

78 child process is running!

79 globVar=8,var=4

80 child 's globVar = 8 , var = 4

81 parent process is running

82 globVar=9,var=1

83 parent process is running

84 globVar=10,var=2

85 parent process is running

86 globVar=11,var=3

87 parent process is running

88 globVar=12,var=4

89 parent process is running

90 globVar=13,var=5

91 parent 's globVar = 13 , var = 5 


父进程的var从0开始?



创建守护进程:daemon
    后台运行,没有控制终端与之相连。独立于控制终端,通常周期性的执行某任务。
    Linux大多数服务器就是用守护进程方式实现的。
    如:Internet服务器进程inetd,Web服务器进程http。守护进程在后台执行,相当于windows的系统服务。
    
    守护进程的启动方式:
        1.Linux系统启动时,从启动脚本/etc/rc.d中启动
        2.可以由作业规划进程crond    启动
        3.用户终端执行(通常是shell)
    
    创建守护进程有如下要点:
        1.让进程后台执行。方法是用fork产生一个子进程,然后父进程退出
        2.调用setsid创建一个新的会话。控制终端,登录会话,进程组通常从父进程继承下来,守护进程要摆脱他们,不受他们影响,
        方法是调用setsid是进程成为一个会话组长。    
            当进程本来就是会话组长时,setsid会调用失败,但第一点保证了不是会话组长,调用成功后,进程成为了新的会话组长和进程组长,并与原来的登录会话和进程组相脱离。
        3.禁止进程重新打开终端。1,2已经使进程成为了一个无终端的会话组长,但是可以重新打开一个终端。可以通过使进程不再是会话组长来实现。再一次调用fork创建新子进程,使父进程退出。
        4.关闭不再需要的文件描述符。
        5.将当前目录更改为艮目录
        6.将文件创建时使用的屏蔽字设置为0
        7.处理SIGCHLD信号。
        
        eg:    ...
            
    进程退出:
    正常退出
        1,main函数执行return
        2,调用exit函数
        3,调用_exit函数
    异常退出:
        1,调用abort函数
        2,进程收到某个信号,该信号使进程终止。
        不管使用哪种方式,都会执行内核中的同一段代码。这段代码用来关闭进程打开的文件描述符,释放它所占用的内存和其他资源。
    退出方式比较:
        exit与return。exit把控制权交给系统,return交给函数
        exit与abort:exit正常终止,abort异常终止
        exit(int exit_code),0为正常终止,其他是异常终止
        exit()与_exit(),exit()在stdlib.h声明,_exit()在unistd.h声明。
        _exit();执行立即返回给内核,exit()会先执行一段清除操作。
        
        
    父子进程终止的先后顺序不同会产生不同结果:
        子进程退出前父进程先退出,子进程交给init进程接管。
        子进程先于父进程终止,父进程没有调用wait等待子进程结束,子进程进入僵死状态,并且会一直保持下去除非重启系统。
        子进程处于僵死状态时,内核只保存该进程的一些必要信息以备父进程所需。此时子进程始终占用着资源,同时也减少了系统可以创建的最大进程数。
        子进程先于父进程终止,且父进程调用了wait或waitpid函数,则父进程会等待子进程的结束。
       
       
    执行新程序:
      使用fork或者vfork创建子进程后,子进程通常会调用exec来执行另一个程序。系统调用exec用于执行一个可执行程序以替代当前进程的执行映像。
      exec并没有生成新进程,一个进程一旦调用了exec,它本身就死亡了,系统把代码段替换成新的程序代码。废弃原有的数据段和堆栈段,唯一保留的是进程id。
      linux下的exec有六种调用方式:声明在头文件unistd.h中

 1          #include <unistd.h>

 2 

 3        extern char **environ;

 4 

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

 6        int execlp(const char *file, const char *arg, ...);

 7        int execle(const char *path, const char *arg,

 8                   ..., char * const envp[]);

 9        int execv(const char *path, char *const argv[]);

10        int execvp(const char *file, char *const argv[]);

     为了更好的使用exec族,用到了环境变量的概念
          环境变量:为了使用户灵活使用shell,环境变量包括用户主目录,终端类型,当前目录,他们定义在用户的工作环境里,用env查看环境变量的值。
          获得环境变量:
          eg:......
          执行新程序后除了保持进程id,父进程id,实际用户id,实际组id外:还有:
              当前工作目录,根目录,创建文件时使用的屏蔽字,进程信号屏蔽字,未决警告,和进程相关的使用处理器的时间,控制终端,文件锁。
          
          等待进程结束:
          wait  等待第一个终止的子进程
          waitpid  等待特定pid终止的子进程
          
          进程的其他操作:
           1.获得进程id getpid()
           2.设置实际用户id和有效用户id setuid ,设置实际组和有效组Id,setgid
              su命令通过这个函数实现
          改变进程的优先级
            getpriority() 返回一组进程的优先级
            setpriority()
          nice()函数
          
          
          实现自己的myshell-------
         


   

你可能感兴趣的:(linux)