linux系统编程:进程

程序与进程

进程与程序区别

No.进程程序
1动态静态
2有生命周期指令集合
3只能对应一个程序可以对应多个进程

概念

进程:程序在计算机上的一次执行过程,执行中的程序。

  • 进程是一个抽象概念
No.组成含义类比
1一个独立的逻辑控制流独占处理器工人/机器
2一个私有的地址空间独占存储器系统工厂
  • 本质
    • 程序在地址空间中按照代码逻辑控制流执行
    • 资源分配最小单位

从程序到进程

  1. 内核将程序读入内存,为程序镜像分配内存空间。
  2. 内核为该进程分配进程标志符PID。
  3. 内核为该进程保存PID及相应的进程状态信息。

进程控制块(PCB):保存进程控制信息

程序格式ELF

ELF(Executable and Linkable Format)文件格式,一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。

查看程序(ELF文件):readelf -S 文件名
查看进程空间大小:size 文件名

虚拟存储器/虚拟地址空间

虚拟存储器

段名组成来源
代码段.text可执行文件
数据段.data bss可执行文件
堆栈段heap stack请求
变量位置
经过初始化的全局变量和静态变量.data
未经初始化的全局变量和静态变量.bss
函数内部声明的局部变量stack
const修饰的全局变量.text
const修饰的局部变量stack
字符串常量.text

.bss(Block Started by Symbol)存放程序中未初始化的全局变量和静态变量,程序执行之前BSS段会自动清0。

详细资料
进程状态

img

类似视频/音频播放器

No.状态含义
1就绪(Ready)进程已获得到除CPU以外的所有必要的资源,获得CPU立即执行
2运行(Running)程序正在CPU上执行
3阻塞(Blocked)等待某个事件发生而无法执行时,放弃CPU

如何查看进程

No.OS命令e.g.
1Windowstasklisttasklist /FI "PID eq 进程PID"
2Linuxps / pstree / top-
ps命令
  • 查看某进程
    通过进程PID查看:ps -p 进程PID
    通过命令行查看:ps -C 命令行
  • 查看进程
No.风格命令属性说明
1BSD风格ps auxa: 终端上所有用户的进程;u:以用户为中心显示详细信息,x:无终端进程
2System V风格ps -efe:所有进程;f:树状显示

Unix从操作风格分为System V风格和BSD风格。它们在目录结构、脚本、命令行等方面存在一些差异。

  • System V, 曾经也被称为AT&T SystemV,是Unix操作系统众多版本中的一支。它最初由AT&T开发。
  • BSD(BerkeleySoftware Distribution,伯克利软件套件)是Unix的衍生系统,1970年代由伯克利加州大学(UniversityofCalifornia, Berkeley)开发。
    随着一些并不基于这两者代码的UNIX实现的出现,例如Linux,这一归纳不再准确。像POSIX这样的标准化努力一直在试图减少各种实现之间的不同。
  • 列表示说明
No.标识含义
1USER用户
2PID进程ID
3%CPU进程占用的CPU百分比
4%MEM占用内存的百分比
5VSZ进程虚拟大小
6RSS常驻内存(内存中页的数量)
7TTY终端ID
8STAT进程状态
9START启动进程的时间
10TIME进程消耗CPU的时间
11COMMAND命令的名称和参数
  • 进程状态标识
No.标识含义
1D不可中断Uninterruptible(usually IO)
2R正在运行,或在队列中的进程
3S处于休眠状态
4T停止或被追踪
5Z僵尸进程
6W进入内存交换(从内核2.6开始无效)
7X死掉的进程
8<高优先级
9n低优先级
10s包含子进程
11+位于后台的进程组
pstree命令

以树状图的方式展现进程之间的派生关系

  • 安装
yum install psmisc
top命令

实时显示系统中各个进程的资源占用,类似Windows任务管理器。

Linux一切皆文件,在/proc/下也可以查看到进程。

如何创建进程

No.OS命令
1Windows程序名
2Linux程序名

如何杀死进程

No.OS命令
1Windowstaskkill /F /PID 进程标识/taskkill /F /IM 程序名
2Linuxkill 进程标识PID

进程操作接口

获取PID

进程标识pid:进程身份证号

No.函数接口
1pid_t getpid()获取当前进程ID
2pid_t getppid()获取当前进程父进程ID
  • 示例
#include <stdio.h>
#include <unistd.h>
 
int main(){
    printf("PID:%dPPID:%d\n",getpid(),getppid());
}

查看进程的PID和PPID

ps -o pid,ppid,cmd,s

如何创建进程

1 分叉函数pid_t fork()
  • 返回值
No.返回值含义
1-1失败
20子进程逻辑控制流
3其他(子进程PID)父进程逻辑控制流
  • 特点
    • 调用一次,返回两次
    • 相同但是独立的地址空间
    • 并发执行
    • 共享文件
  • 示例

调用一次,返回两次

#include <stdio.h>
#include <unistd.h>
 
int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();//子进程的PID
    fork();
    if(pid == 0){// child
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }else{//father
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }
    for(;;);
}

相同但是独立的地址空间&并发执行

#include <stdio.h>
#include <unistd.h>
int i = 100;
int main(){
    int j=100;
    pid_t pid = fork();
    if(pid == 0){// child
        int k;
        for(k=0;k<10000;k++)
            printf("this is childi%d\t j%d\n",++i,++j);
    }else{
        int k;
        for(k=0;k<10000;k++)
            printf("this is fatheri%d\t j%d\n",--i,--j);
    }
}

共享文件

#include <stdio.h>
#include <unistd.h>
int i = 100;
int main(){
    int j=100;
    FILE* fd = fopen("./test","w+");
    pid_t pid = fork();
    if(pid == 0){// child
        int k;
        for(k=0;k<10000;k++)
            fprintf(fd,"this is childi%d\t j%d\n",++i,++j);
    }else{
        int k;
        for(k=0;k<10000;k++)
            fprintf(fd,"this is fatheri%d\t j%d\n",--i,--j);
    }
}
  • 本质
    复制+分叉
No.概念状态硬件特点
1并发(concurrency)两个或者多个进程在同时存在单核进程指令同时或者交错执行。
2并行(parallellism)两个或者多个进程在同时执行多核一种特殊的并发
2 执行函数exec()
  • 分类
No.分类函数
1字符串数组参数execv()execvp()execve()
2可变参数execle()execlp()execl()
  • exec函数组名字规律
No.字符含义
1v第二个参数是数组
2l第二个参数之后是变参
3p第一个参数是文件名
4e最后一个参数是环境变量
  • 返回值
No.返回值含义
1-1失败
2不返回成功
  • 特点
    • 一次调用,失败返回
    • 改朝换代,取而代之
      • PID不变
      • 地址空间内容变化
  • 示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
extern char ** environ;
 
int main(int argc,char** argv){
    printf("%s,PID %d\n",argv[0],getpid());
    //execl("/bin/ps","ps","-a","-o","pid,ppid,cmd",0);
    //execlp("ps","ps","-a","-o","pid,ppid,cmd",0);
    //execle("/bin/ps","ps","-a","-o","pid,ppid,cmd",0,environ);
    char* args[] = {"ps","-a","-o","pid,ppid,cmd",0};
    //execv("/bin/ps",args);
    execve("/bin/ps",args,environ);
    //execvp("ps",args);
    printf("%s,PID %d\n",argv[0],getpid());
}
/*
./a.out,PID 4275  //执行命令的进程
  PID  PPID CMD     //execve执行args[]命令
 3790  3604 su - root
 3793  3790 -bash
 4275  3793 ps -a -o pid,ppid,cmd
 */
  • 本质
    覆盖程序
3 系统函数int system(Shell字符串)
  • 返回值
No.返回值含义
1-1失败
2127无法启动shell来运行
3其他命令退出码
  • 特点
    一次调用,一次返回
  • 示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(int argc,char** argv){
    printf("PID:%d\n",getpid());    
    system("sleep 3&");
    printf("PID:%d\n",getpid());    
}
  • 本质
    shell执行命令/程序

Linux系统可以创建多少个进程?使用ulimit -a可以查看到。可以通过ulimit -u 进程数修改。
更详细的设置可以在/etc/security/limits.conf修改。

如何结束进程

No.方式说明
1main函数退出只能用在main函数内
2调用exit()函数一般用在main函数以外的函数
3调用_exit()函数一般用来结束子进程
4调用abort()函数一般用来异常退出
5信号终止终止其他进程
  • 示例

return退出

#include <stdio.h>
#include <unistd.h>
 
int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());     
    return 100;
}

exit()/abort()退出

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    //exit(EXIT_FAILURE);
    abort();
}

如何停止进程

休眠
int sleep(unsigned int secs)
  • 参数
    secs指定休眠的秒数,-1表示永久休眠
  • 返回值
    未休眠的秒数
  • 特性
    如果没有信号中断,休眠指定秒数返回0,否则马上返回未休眠的秒数。
  • 示例:电子时钟
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <strings.h>
 
int main(){
    int i = 0;
    for(;;){
        time_t t;
        time(&t);
        struct tm* ptm =  gmtime(&t);
        char buf[BUFSIZ];
        bzero(buf,BUFSIZ);
        strftime(buf,BUFSIZ,"%P %T",ptm);
        printf("\r%s",buf);
        fflush(stdout);
        sleep(1);
    }
}
暂停
int pause()
  • 返回值
    总是-1
  • 特性
    • 如果程序没有处理信号,直接中断,执行默认信号处理,程序后续代码不再执行。
    • 如果程序存在信号处理,执行信号处理后,执行后续代码。
  • 等待信号
No.快捷键信号说明
1Ctrl+CSIGINT中断
2Ctrl+ZSIGTSTP终端的停止信号
  • 示例
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void test(int sig){
    printf("revc a signal%d",sig);
}
 
int main(){
    signal(SIGINT,test);
    printf("before pause\n");
    pause();
    printf("after pause\n");
}
等待
  • pid_t wait(int* status):等价pid_t waitpid(-1,stauts,0)
  • pid_t waitpid(pid_t pid,int * status,int options)
  • 参数
No.参数含义说明
1pid等待的进程<-1:等待进程组为pid的所有进程;-1: 等待任何子进程;0:等待同组的进程;>0:进程为pid 的子进程
2status子进程结束状态判断正常结束:使用WIFEXITED(status);判断异常结束使用WIFSIGNALED(status);判断暂停使用WIFSTOPPED(status)
3options选项WNOHANG若子进程没有结束,返回0,不予以等待;若子进程结束,返回该子进程的ID。WUNTRACED若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。
  • 返回值
No.返回值含义
1-1失败
2其他等待的PID
  • 正常结束:WIFEXITED(status)
No.参数含义
10正常结束子进程
20非正常结束子进程

WEXITSTATUS(status)取得子进程exit()返回的结束代码
一般会先用WIFEXITED来判断是否正常结束才能使用此宏

  • 异常结束:WIFSIGNALED(status)
No.参数含义
10异常结束子进程
20非异常结束子进程

WTERMSIG(status)取得子进程因信号而中止的信号代码
一般会先用 WIFSIGNALED 来判断后才使用此宏

  • 暂停:WIFSTOPPED(status)
No.参数含义
10暂停结束子进程
20非暂停结束子进程

WSTOPSIG(status)取得引发子进程暂停的信号代码
一般会先用 WIFSTOPPED 来判断后才使用此宏。

  • 示例
    父进程等待子进程退出
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
 
int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    if(pid == 0){// child
        sleep(2);
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        //exit(0);
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        printf("pid:%d exit\n",waitpid(pid,NULL,0));
    }
}

更加安全的方式

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
 
void handler(int sig){
    int status;
    pid_t cpid = wait(&status);
    if(WIFEXITED(status)){
        printf("child exit by %d\n",WEXITSTATUS(status));
    }   
    if(WIFSIGNALED(status)){
        printf("child exit by signal %d\n",WTERMSIG(status));
    }
    printf("child %d exit\n",cpid);
}
 
int main(){
    signal(SIGCHLD,handler);
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    if(pid == 0){// child
        sleep(2);
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        //exit(0);
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        printf("leave:%d\n",sleep(5));
        //exit(200);
    }
    for(;;);
}

如果不关心退出的情况。

#include <stdio.h>
#include <unistd.h>
#include <wait.h>
 
void handler(int sig){
    pid_t cpid = wait(NULL);
    printf("child %d exit",cpid);
}
 
int main(){
    signal(SIGCHLD,handler);
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    if(pid == 0){// child
        sleep(2);
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }
}

进程的终老、他杀与自杀


特殊进程
No.概念出现条件导致结果是否有害
1孤儿进程父进程先于子进程退出init进程作为新的父进程无害
2僵尸进程子进程退出,父进程没有获取子进程的状态信息调用waitwaitpid有害,避免出现僵尸进程
  • 示例
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
void handle(int sig){
    //wait(NULL);
    printf("this is child exit %d",sig);
}
int main(){
    signal(SIGCHLD,handle);
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    if(pid == 0){// child
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
        for(;;);
    }
}

作者:jdzhangxin

链接:https://www.jianshu.com/p/011bd63208ef

来源:简书

简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值