Linux系统编程1:进程基础

从一次元世界进入二次元世界,不只是思维的改变,而是世界观的改变。

程序与进程

如果程序是菜谱,进程就是厨师烹饪;
如果程序是乐谱,进程就是乐师演奏;
如果程序是剑谱,进程就是剑客舞剑;
如果程序是棋谱,进程就是棋士复盘;
程序是静的,进程是动的。

进程与程序区别

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

概念

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

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

从代码到程序

从程序到进程

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

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

程序格式ELF

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

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

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

虚拟存储器

gdb查看内存映射信息:info proc mapping

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

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

详细资料

进程状态

类似视频/音频播放器

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

如何查看进程

No. OS 命令 e.g.
1 Windows tasklist tasklist /FI "PID eq 进程PID"
2 Linux ps / pstree / top -

ps命令

  • 查看某进程

    1. 通过进程PID查看:ps -p 进程PID
    2. 通过命令行查看:ps -C 命令行
    3. 查看程序的PID: pidof 程序名
  • 查看进程

No. 风格 命令 属性说明
1 BSD风格 ps aux a: 终端上所有用户的进程;u:以用户为中心显示详细信息,x:无终端进程
2 System V风格 ps -ef e:所有进程;f:树状显示

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

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

pstree命令

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

  • 安装
yum install psmisc

top命令

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

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

如何创建进程

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

如何杀死进程

No. OS 命令
1 Windows taskkill /F /PID 进程标识/taskkill /F /IM 程序名
2 Linux kill 进程标识PID

进程操作接口

获取PID

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

No. 函数 接口
1 pid_t getpid() 获取当前进程ID
2 pid_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 失败
2 0 子进程逻辑控制流
3 其他(子进程PID) 父进程逻辑控制流
  • 特点

    • 调用一次,返回两次
    • 相同但是独立的地址空间
    • 并发执行
    • 共享文件
  • 示例

调用一次,返回两次

#include <stdio.h>
#include <unistd.h>
 
int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    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(;;);
}

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

#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. 字符 含义
1 v 第二个参数是数组
2 l 第二个参数之后是变参
3 p 第一个参数是文件名
4 e 最后一个参数是环境变量
  • 返回值
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());
}

可以通过which查看命令的位置。

  • 本质
    覆盖程序

3 系统函数int system(Shell字符串)

  • 返回值
No. 返回值 含义
1 -1 失败
2 127 无法启动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. 方式 说明
1 main函数退出 只能用在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. 快捷键 信号 说明
1 Ctrl+C SIGINT 中断
2 Ctrl+Z SIGTSTP 终端的停止信号
  • 示例
#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. 参数 含义 说明
1 pid 等待的进程 <-1:等待进程组为pid的所有进程;-1: 等待任何子进程;0:等待同组的进程;>0:进程为pid 的子进程
2 status 子进程结束状态 判断正常结束:使用WIFEXITED(status);判断异常结束使用WIFSIGNALED(status);判断暂停使用WIFSTOPPED(status)
3 options 选项 WNOHANG若子进程没有结束,返回0,不予以等待;若子进程结束,返回该子进程的ID。WUNTRACED若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。
  • 返回值
No. 返回值 含义
1 -1 失败
2 其他 等待的PID
  • 正常结束:WIFEXITED(status)
No. 参数 含义
1 0 正常结束子进程
2 0 非正常结束子进程

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

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

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

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

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());
    }
}

进程的终老、他杀与自杀

  1. 如何kill多个进程?kill -9 PID PID...
  2. 如何按名字kill进程?pkill 进程名 \ killall 进程名 \ kill -9 $(pidof 进程名)

特殊进程

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(;;);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容

  • 计算机系统漫游 代码从文本到可执行文件的过程(c语言示例):预处理阶段,处理 #inlcude , #defin...
    willdimagine阅读 3,545评论 0 5
  • Linux 进程管理与程序开发 进程是Linux事务管理的基本单元,所有的进程均拥有自己独立的处理环境和系统资源,...
    JamesPeng阅读 2,442评论 1 14
  • 一、进程的创建和调度 相关概念: 最基础的计算机动作被称为指令(instruction)。 程序(program)...
    穹蓝奥义阅读 4,632评论 0 6
  • 一、Linux系统概述 不加引号可理解为宏,直接替换,单引号中特殊字符会被解释为普通字符,双引号中$,,'还是特殊...
    赤果_b4a7阅读 1,449评论 0 2
  • 进程基础 a.从程序到进程 1.内核将程序读入内存,为程序镜像分配内存空间。 2.内核为该进程分配进程标识符PID...
    allen_TZ阅读 465评论 2 1