Linux系统编程之进程介绍

目录

(一)进程的基本问题

(1)什么是程序,什么是进程,有什么区别?

程序是指通过编译后再磁盘生成的拥有一定权限的文件,是静态概念,例如在终端中输入“gcc *.c -o pro”,生成的pro文件则被称为一个程序。进程是程序的一次运行活动,当程序运行时,会向系统申请内存,系统则多出一个进程,是动态概念。

(2)如何查看系统中有哪些进程?

  1. 使用ps指令查看,实际工作中,配合grep来查找程序中是否存在某一个进程。例如通过gcc编译生成的test文件。通过“ps -aux|grep test”搜寻相关进程,可以找到对应的进程编号3819.
#include <stdio.h>
int main(){
        while(1){
        }
        return 0;
}
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ gcc test.c -o test
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ ./test
lamda@lamda-virtual-machine:~$ ps -aux|grep test
kernoops     933  0.0  0.0  11264   444 ?        Ss   10:19   0:00 /usr/sbin/kerneloops --test
lamda       3819  100  0.0   2380   592 pts/2    R+   13:43   1:13 ./test
lamda       3827  0.0  0.0  12120   720 pts/3    S+   13:44   0:00 grep --color=auto test
  1. 使用top指令查看,类似windows任务管理器,top指令会动态查询当前进程,运行内存较大的会排在前面。
lamda@lamda-virtual-machine:~$ top
top - 13:50:22 up  3:31,  2 users,  load average: 0.30, 0.22, 0.14
Tasks: 280 total,   2 running, 278 sleeping,   0 stopped,   0 zombie
%Cpu(s): 53.5 us,  0.3 sy,  0.0 ni, 46.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   3889.9 total,   1863.8 free,   1013.5 used,   1012.6 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.   2616.3 avail Mem 
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                       
   3829 lamda     20   0    2380    524    444 R  99.7   0.0   0:23.61 test                                                                                                          
   1882 lamda     20   0 4264832 395332 145940 S   3.7   9.9   1:16.56 gnome-shell                                                                                                   
   1741 lamda     20   0  307764 118692  63988 S   3.0   3.0   0:26.05 Xorg                                                                                                          
   3081 lamda     20   0  823712  55544  39940 S   1.3   1.4   0:07.74 gnome-terminal-                                                                                               
    405 root     -51   0       0      0      0 S   0.3   0.0   0:00.40 irq/16-vmwgfx                                                                                                 
   3437 root      20   0       0      0      0 I   0.3   0.0   0:02.82 kworker/1:1-events                                                                                            
   3830 lamda     20   0   15072   4172   3356 R   0.3   0.1   0:00.14 top                                                                                                           
      1 root      20   0  169704  12972   8340 S   0.0   0.3   0:03.28 systemd                                                                                                       
      2 root      20   0       0      0      0 S   0.0   0.0   0:00.04 kthreadd                                                                                                      
      3 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_gp                                                                                                        
      4 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 rcu_par_gp                                                                                                    
      5 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 slub_flushwq                                                                                                  
      6 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 netns                                                                                                         

(3)什么是进程标识符?

每个进程都有一个非负整数表示的唯一ID,叫做pid,编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符。
Pid=0: 称为交换进程(swapper),作用:进程调度
Pid=1:init进程,作用:系统初始化

(4)父进程,子进程,孤儿进程和僵尸进程?

  1. 如果进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类中的父子关系。
  2. 子进程退出状态不被收集,变成僵死进程(僵尸进程),查询进程状态会显示Z+。
  3. 父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。(Ubuntu图形界面终端中由shell内核控制,所以在图形界面看到的init进程编号不一定是1,如果转换到字符界面,则编号会变为1)

(5)C程序的存储空间是如何分配?

在这里插入图片描述
《UNIX环境高级编程》书中指出,对于一个标准的C程序,系统会在编译后对其进行内存分配,地址由低到高依次是:

  1. 正文,预处理后系统将正文中的注释去掉,保存剩余的代码正文;
  2. 初始化的数据,该部分指代全局变量中已初始化的数据;
  3. 未初始化数据,全局变量中未初始化的数据,被称为bss(block started by symbol);
  4. 堆,函数中通常会申请数组或字符串等动态内存分配,一般都以堆的形式存放;
  5. 栈,函数中存放的其他数据形式;
  6. 命令行参数和环境变量,可直接与系统进行操作的命令。

当创建一个子进程后,子进程通常会复制父进程的全部代码变量,开辟一个进程进行操作,目前更流行使用写时复制操作(COW,copy on write)技术,这些区域由父进程和子进程共享,访问权限都是只读,当某一方试图修改这些区域,内核会只为修改该区域的内存制作副本。

(二)进程创建及收集退出状态

(1)使用fork创子进程

fork函数调用成功,返回两次。返回值为0,代表当前进程是子进程;返回值非负数,代表当前进程为父进程;调用失败,返回-1。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(){
        pid_t pid;
        pid = getpid();
        printf("my code pid = %d\n",pid);
        int num = 0;
        pid = fork();
        if(pid==0){
                sleep(1);
                num++;
                printf("I think this is child pid, pid = %d,num = %d\n",getpid(),num);
        }
        else if(pid>0) {
                wait(NULL);
                printf("I think this is father pid, pid = %d,num = %d\n",getpid(),num);
        }
        else{
                printf("fork error.\n");
        }

                return 0;
}

运行程序查看父子进程编号,发现调用fork函数后,后面的代码内容运行了两次,父子进程同时拥有fork函数之前的变量,但不共享,例如num,子进程中修改了num变量,父进程没有任何变化。

lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ gcc demo_fork.c -o fork
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ ./fork
my code pid = 3927
I think this is child pid, pid = 3928,num = 1
I think this is father pid, pid = 3927,num = 0

(2)使用vfork创子进程

vfork与fork的区别在于,vfork 直接使用父进程存储空间,不拷贝;vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。需要注意的是,如果子进程以break等非正常形式退出,则子进程共享的变量会被破坏掉。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
        pid_t pid;
        pid = getpid();
        printf("my code pid = %d\n",pid);
        int num = 0;
        if(vfork()==0){
                sleep(1);
                num++;
                printf("I think this is child pid, pid = %d,num = %d\n",getpid(),num);
                exit(0);
        }
        else {
                //wait(NULL);
                printf("I think this is father pid, pid = %d,num = %d\n",getpid(),num);
        }
                return 0;
}

lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ ./vfork
my code pid = 4064
I think this is child pid, pid = 4065,num = 1
I think this is father pid, pid = 4064,num = 1

(3)收集退出状态

  1. 正常退出

Main函数调用return;
进程调用exit(),标准c库;
进程调用_exit()或者_Exit(),属于系统调用;
进程最后一个线程返回;
最后一个线程调用pthread_exit;

  1. 异常退出

调用abort;
当进程收到某些信号时,如ctrl+C;
最后一个线程对取消(cancellation)请求做出响应;

采用wait函数收集子进程退出状态:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
        pid_t pid;
        pid = getpid();
        int status;
        printf("my code pid = %d\n",pid);
        int num = 0;
        if(fork()==0){
                sleep(1);
                num++;
                printf("I think this is child pid, pid = %d,num = %d\n",getpid(),num);
        //      exit(3);
                return 3;
        }
        else {
                wait(&status);
                printf("I think this is father pid, pid = %d,num = %d\n",getpid(),num);
                if(WIFEXITED(status))
                printf("Child pid return num is %d\n",WEXITSTATUS(status));
                else
                perror("Why:");
        }
                return 0;
}

运行结果如下,return 和 exit 、_exit()、_Exit()函数退出都会被收集到:

lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ gcc demo_forkexit.c -o fork_exit
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ ./fork_exit 
my code pid = 4168
I think this is child pid, pid = 4169,num = 1
I think this is father pid, pid = 4168,num = 0
Child pid return num is 3

其中宏 WIFEXITED 和 WEXITSTATUS 都是用来解析 wait 函数收集的退出状态。
在这里插入图片描述

(4)等待子进程退出

一般创建子进程会需要新的进程完成一定操作后返回相关的数值,但父进程和子进程同时争夺运行资源,可能父进程在子进程还没结束时退出,则子进程会变成孤儿进程。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main(){
    pid_t pid = fork();
    if(pid > 0){
        printf("PARENT : pid = %d, ppid = %d \n", getpid(), getppid());
    }else if(pid == 0){
    for(int i=0; i<3; i++){
        printf("i = %d, pid = %d, ppid = %d \n", i, getpid(), getppid());
        sleep(1);
    }
    exit(3);
    }
    return 0;
}
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ ./guer
PARENT : pid = 4246, ppid = 3088 
i = 0, pid = 4247, ppid = 4246 
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ i = 1, pid = 4247, ppid = 1652 
i = 2, pid = 4247, ppid = 1652 

这里为什么父进程结束后给子进程找到的新的父进程是1652,原因在于Ubuntu图形界面系统的终端仍然是shell控制,并不是直接由系统控制,如果转到字符界面则可以获得父进程为1。
参考博文:关于图形化界面详细解释

(三)进程中调用其他程序运行

(1)popen函数

有些情况下,进程的运行过程可能还会调用其他程序运行,常见的有exec族函数,system函数和popen函数,下面用popen函数调用ls命令,并将ls命令得到的数据存放到新的文件中。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(){
        pid_t pid;
        pid = getpid();
        printf("my code pid = %d\n",pid);
        int status;
        int num = 0;
        if(fork()==0){
                sleep(1);
                printf("I think this is child pid, pid = %d,num = %d\n",getpid(),num);
                printf(" I can use 'ls' command.\n");
                //FILE *popen(const char *command, const char *type);
                FILE *fps;
                fps = popen("ls","r");
                //size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
                //size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
                char readbuf[1024]={0};
                int nread = fread(readbuf,sizeof(char),1024,fps);
                pclose(fps);
                fps = fopen("./ls.txt","w");
                fwrite(readbuf,sizeof(char),strlen(readbuf),fps);
                fclose(fps);
                exit(0);
        }
        else {
                wait(&status);
                printf("I think this is father pid, pid = %d,num = %d\n",getpid(),num);
                if(WIFEXITED(status))
                printf("The status of child pid is %d\n",WEXITSTATUS(status));
        }
                return 0;
}
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ ./popen
my code pid = 4307
I think this is child pid, pid = 4308,num = 0
I can use 'ls' command.
I think this is father pid, pid = 4307,num = 0
The status of child pid is 0
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ cat ls.txt
a.out
demo4.c
demo_forkapply.c
demo_fork.c
demo_forkexit.c
demo_guer.c
demo_popen.c
demo_vfork.c
fork
fork_exit
guer
ls.txt
popen
test
test.c
vfork

(2)system函数

system函数调用十分简单,只需要将命令以字符串形式存放即可,但没有返回值,无法将所得数据进行保存。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
int main(){
        pid_t pid;
        pid = getpid();
        printf("my code pid = %d\n",pid);
        int status;
        int num = 0;
        if(fork()==0){
                sleep(1);
                printf("I think this is child pid, pid = %d,num = %d\n",getpid(),num);
                printf(" I can use 'ls' command.\n");
                system("ls");
                exit(0);
        }
        else {
                wait(&status);
                printf("I think this is father pid, pid = %d,num = %d\n",getpid(),num);
                if(WIFEXITED(status))
                printf("The status of child pid is %d\n",WEXITSTATUS(status));
        }
                return 0;
}   
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ gcc demo_system.c -o system
lamda@lamda-virtual-machine:~/Desktop/code/Linux/Pid$ ./system
my code pid = 4446
I think this is child pid, pid = 4447,num = 0
I can use 'ls' command.
a.out	 demo_forkapply.c  demo_forkexit.c  demo_popen.c   demo_vfork.c  fork_exit  ls.txt  system  test.c
demo4.c  demo_fork.c	   demo_guer.c	    demo_system.c  fork		 guer	    popen   test    vfork
I think this is father pid, pid = 4446,num = 0
The status of child pid is 0

(3)exec族函数

参考博文:exec族函数介绍
函数族:
  exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
函数原型:
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[]);
返回值:
  exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
同样可以调用其它程序进行操控,具体使用方法参考上述博文,但一般情况下,使用system函数更为简洁方便。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值