专栏首页电子技术研习社Linux笔记(10)| 进程概述

Linux笔记(10)| 进程概述

fork函数

pid_t fork(void)

父进程返回正整数,子进程返回0,在执行fork函数之前,操作系统只有一个进程,fork函数之前的,代码只会被执行一次,在执行fork函数之后,操作系统有两个几乎一样的进程,fork函数之后的代码会被执行两次

子进程偷梁换柱

(1)execl和execv

这两个函数是最基本的exec,都可以用来执行一个程序,区别是传参的格式不同。execl是把参数列表(本质上是多个字符串,必须以NULL结尾)依次排列而成(l其实就是list的缩写),execv是把参数列表事先放入一个字符串数组中,再把这个字符串数组传给execv函数。

(2)execlp和execvp

这两个函数在上面2个基础上加了p,较上面2个来说,区别是:上面2个执行程序时必须指定可执行程序的全路径(如果exec没有找到path这个文件则直接报错),而加了p的传递的可以是file(也可以是path,只不过兼容了file。加了p的这两个函数会首先去找file,如果找到则执行执行,如果没找到则会去环境变量PATH所指定的目录下去找,如果找到则执行如果没找到则报错)

(3)execle和execvpe

这两个函数较基本exec来说加了e,函数的参数列表中也多了一个字符串数组envp形参,e就是environment环境变量的意思,和基本版本的exec的区别就是:执行可执行程序时会多传一个环境变量的字符串数组给待执行的程序。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>  
#include <sys/wait.h>
#include <stdlib.h>



int main(void)
{
    pid_t pid = -1;
    pid_t ret = -1;
    int status = -1;

    pid = fork();
    if (pid > 0)
    {
        // 父进程
        printf("parent, 子进程id = %d.\n", pid);
    }
    else if (pid == 0)
    {
        // 子进程
        //execl("/bin/ls", "ls", "-l", "-a", NULL);     // ls -l -a
        //char * const arg[] = {"ls", "-l", "-a", NULL};
        //execv("/bin/ls", arg);

        //execl("hello", "aaa", "bbb", NULL);
        //char * const arg[] = {"aaa", "bbb", NULL};
        //execv("hello", arg);

        //execlp("ls", "ls", "-l", "-a", NULL); 
        char * const envp[] = {"AA=aaaa", "XX=abcd", NULL};
        execle("hello", "hello", "-l", "-a", NULL, envp);

        return 0;
    }
    else
    {
        perror("fork");
        return -1;
    }

    return 0;
}

进程的退出

_exit:立即退出,不处理IO缓冲区

exit:会先处理IO缓冲区

等待子进程的终结

pid_t wait(int *status)

WIFEXITED、WIFSIGNALED、WEXITSTATUS,这几个宏用来获取子进程的退出状态。

WIFEXITED:宏用来判断子进程是否正常终止(return、exit、_exit退出),如果子进程正常退出,则该宏为真

WIFSIGNALED:宏用来判断子进程是否非正常终止(被信号所终止)

WEXITSTATUS:宏用来得到正常终止情况下的进程返回值的

几个概念

进程组

作用:对相同类型的进程进行管理

进程组的诞生

在Shell里面直接执行一个应用程序,对于大部分进程来说,自己就是进程组的首进程,进程组只有一个进程

如果进程调用了fork函数,那么父子进程同属一个进程组,父进程为首进程

在Shell中通过管道执行连接起来的应用程序,两个程序同属一个进程组,第一个程序为进程组的首进程

进程组id:pgid,由首进程pid决定

会话

作用:管理进程组

会话的诞生

调用setsid函数,新建一个会话,应用程序作为会话的第一个进程,称为会话首进程

用户在终端正确登录之后,启动shell时linux系统会创建一个新的会话,shell进程作为会话首进程

会话id:会话首进程id,SID

前台进程组

Shell进程启动时,默认是前台进程组的首进程。前台进程组的首进程会占用会话所关联的终端来进行,shell启动其他应用程序时,其他程序成为首进程

后台进程组

后台进程中的程序是不会占用终端在shell进程里启动程序时,加上&符号可以指定程序运行在后台进程组里面

前台切换到后台

ctrl+z

jobs:查看有哪些后台进程组

fg+job id 可以把后台进程组切换为前台进程组

终端:

1、物理终端:串口终端,lcd终端

2、伪终端:ssh远程连接产生的终端、桌面系统启动的终端

3、虚拟终端:Linux内核自带的,ctrl+alt+f6可以打开7个虚拟终端

守护进程

会话用来管理前后台进程组,会话一般关联着一个终端当终端被关闭了之后,会话中的所有进程都会被关掉

守护进程不受终端影响。就算终退出,也可以继续在后台运行

如何来写一个守护进程

1.创建一个子进程,父进程直接退出

方法通过fork()函数

2.创建一个新的会话,摆脱终端的影响

方法通过setsid函数

3.改变守护进程的当前工作目录,改为"/"

方法通过chrdir()函数

4.重设文件权限掩码

新建文件的权限受文件权限掩码影响

022:只写

新建文件默认执行权限:666

真正的文件执行权限:666&(~umask)

方法:通过umask

5.关闭不需要的文件描述符

0,1,2:标准输入、输出、错误

// 函数作用就是把调用该函数的进程变成一个守护进程
void create_daemon(void)
{
    pid_t pid = 0;

    pid = fork();
    if (pid < 0)
    {
        perror("fork");
        exit(-1);
    }
    if (pid > 0)
    {
        exit(0);        // 父进程直接退出
    }

    // 执行到这里就是子进程

    // setsid将当前进程设置为一个新的会话期session,目的就是让当前进程
    // 脱离控制台。
    pid = setsid();
    if (pid < 0)
    {
        perror("setsid");
        exit(-1);
    }

    // 将当前进程工作目录设置为根目录
    chdir("/");

    // umask设置为0确保将来进程有最大的文件操作权限
    umask(0);

    // 关闭所有文件描述符
    // 先要获取当前系统中所允许打开的最大文件描述符数目
    int cnt = sysconf(_SC_OPEN_MAX);
    int i = 0;
    for (i=0; i<cnt; i++)
    {
        close(i);
    }

    open("/dev/null", O_RDWR);
    open("/dev/null", O_RDWR);
    open("/dev/null", O_RDWR);

}

普通进程伪装成守护进程

nohup

ps命令:

如:ps axjf | grep "程序名"

aux

axjf

a:显示一个终端的所有进程

u:显示进程的归属用户及内存使用情况

x:显示没有关联控制终端的进程

j:显示进程归属进程组的id、会话id、父进程id

f:以ascii码形式显示出进程的层次关系

ps aux

user:进程是哪个用户产生的

pid:进程的身份证号码

%cpu:表示进程占用了cpu计算能力的百分比

%mem:表示进程占用了系统内存的百分比

vsz:进程使用的虚拟内存大小

rss:进程使用的物理内存大小

tty:表示进程关联的终端

stat:表示进程当前状态

start:表示进程的启动时间

time:记录进程运行的时间

command:表示进程执行的具体程序

常见的状态有以下几种

-D:不可被唤醒的睡眠状态,通常用于I/O情况。

-R:该进程正在运行。

-S:该进程处于睡眠状态,可被唤醒

-T:停止状态,可能是在后台暂停或进程处于除错状态。

-X:死掉的进程。

-Z:僵尸状态。

-N:低优先级。

-s:进程是会话首进程。

-l:多线程(小写L)。

-+:位于后台。

进程的5种状态

(1)就绪态。这个进程当前所有运行条件就绪,只要得到了CPU时间就能直接运行。

(2)运行态。就绪态时得到了CPU就进入运行态开始运行。

(3)僵尸态。进程已经结束但是父进程还没来得及回收

(4)等待态(浅度睡眠&深度睡眠),进程在等待某种条件,条件成熟后可进入就绪态。等待态下就算你给他CPU调度进程也无法执行。浅度睡眠等待时进程可以被(信号)唤醒,而深度睡眠等待时不能被唤醒只能等待的条件到了才能结束睡眠状态。

(5)暂停态。暂停并不是进程的终止,只是被被人(信号)暂停了,还可以回复的。

ps axjf

ppid:表示进程的父进程id

pid:进程的身份证号码

pgid:进程所在组的id

sid:进程所在会话的id

tty:表示进程关联的终端

tpgid:值为-1,表示进程为守护进程

stat:表示进程当前状态

uid:启动进程的用户id

time:记录进程运行的时间

command:表示进程的层次关系

使用场景:

关注进程本身:ps aux

关注进程间的关系:ps axjf

linux内核提供多种进程间通信机制

(1)无名管道和有名管道

(2)SystemV IPC:信号量、消息队列、共享内存

(3)Socket域套接字

(4)信号

linux的IPC机制-管道

管道(无名管道)

(1)管道通信的原理:内核维护的一块内存,有读端和写端(管道是单向通信的)

(2)管道通信的方法:父进程创建管理后fork子进程,子进程继承父进程的管道fd

(3)管道通信的限制:只能在父子进程间通信、半双工

(4)管道通信的函数:pipe、write、read、close

有名管道(fifo)

(1)有名管道的原理:实质也是内核维护的一块内存,表现形式为一个有名字的文件

(2)有名管道的使用方法:固定一个文件名,2个进程分别使用mkfifo创建fifo文件,然后分别open打开获取到fd,然后一个读一个写

(3)管道通信限制:半双工(注意不限父子进程,任意2个进程都可)

(4)管道通信的函数:mkfifo、open、write、read、close

SystemV IPC介绍

1、SystemV IPC的基本特点

(1)系统通过一些专用API来提供SystemV IPC功能

(2)分为:信号量、消息队列、共享内存

(3)其实质也是内核提供的公共内存

2、消息队列

(1)本质上是一个队列,队列可以理解为(内核维护的一个)FIFO

(2)工作时A和B2个进程进行通信,A向队列中放入消息,B从队列中读出消息。

3、信号量

(1)实质就是个计数器(其实就是一个可以用来计数的变量,可以理解为int a)

(2)通过计数值来提供互斥和同步

4、共享内存

(1)大片内存直接映射

(2)类似于LCD显示时的显存用法

使用syslog来记录调试信息

(1)一般log信息都在操作系统的/var/log/messages这个文件中存储着,但是ubuntu中是在/var/log/syslog文件中的。

syslog的工作原理

(1)操作系统中有一个守护进程syslogd(开机运行,关机时才结束),这个守护进程syslogd负责进行日志文件的写入和维护。

(2)syslogd是独立于我们任意一个进程而运行的。我们当前进程和syslogd进程本来是没有任何关系的,但是我们当前进程可以通过调用openlog打开一个和syslogd相连接的通道,然后通过syslog向syslogd发消息,然后由syslogd来将其写入到日志文件系统中。

(3)syslogd其实就是一个日志文件系统的服务器进程,提供日志服务。任何需要写日志的进程都可以通过openlog/syslog/closelog这三个函数来利用syslogd提供的日志服务。这就是操作系统的服务式的设计。

#include <stdio.h>
#include <syslog.h>
#include <sys/types.h>
#include <unistd.h>


int main(void)
{
    printf("my pid = %d.\n", getpid());


    openlog("b.out", LOG_PID | LOG_CONS, LOG_USER);

    syslog(LOG_INFO, "this is my log info.%d", 23);


    syslog(LOG_INFO, "this is another log info.");
    syslog(LOG_INFO, "this is 3th log info.");

    closelog();
}

本文分享自微信公众号 - 电子技术研习社(zjf18770701843),作者:小小飞飞哥

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • arm(2)| 汇编指令和伪指令

    指令是CPU机器指令的助记符,经过编译后会得到一串10组成的机器码,可以由CPU读取执行。伪指令本质上不是指令(只是和指令一起写在代码中),它是编译器环境提供的...

    飞哥
  • 基于OP07的程控放大器的设计

    Hello 大家好,这里是飞哥有话聊的第③篇原创文章,这篇文章的篇幅稍长一些哦。更多精彩文章以后会持续更新,也欢迎大家如果有什么有趣的文章来投稿哦!文体等等什么...

    飞哥
  • uCOS | 事件

    事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。即一个...

    飞哥
  • Linux性能及调优指南(翻译)之Linux进程管理

    本文为IBM RedBook的Linux Performanceand Tuning Guidelines的1.1节的翻译 原文地址:http://www.re...

    小小科
  • 进程知多少?

    1 进程为什么出现?2 进程的组成3 如何竞争资源(调度算法)3.1 FCFS3.2 RR3.3 SPN3.4 SRT3.5 HRRN3.6 FB4 进程状态4...

    LieBrother
  • 从CPU管理到进程的引入

    为什么要管理CPU,这是因为在“上古时代”,CPU是计算机硬件之中最昂贵的资源。因此提高CPU利用率是很有必要的。我们知道只要给CPU的PC一个地址,CPU就能...

    zy010101
  • Linux进程管理命令及状态详解

    查看进程树。 linux中,每一个进程都是由其父进程创建的。此命令以可视化方式显示进程,通过显示进程的树状图来展示进程间关系。如果指定了pid了,那么树的根是...

    bboy枫亭
  • 孤儿进程和僵尸进程

    我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进...

    武军超
  • 100个Linux命令(7)-进程管理

    这是100个命令的第7篇文章,主要关于进程的管理命令以及你应该知道关于进程的基本原理,相对于命令的理解,应该更加注重对于进程本身的理解。

    懒人的小脑
  • 僵尸进程

      在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等.但是仍然为其保留一定的信息(包括进程号the process ID,退出状态t...

    猿人谷

扫码关注云+社区

领取腾讯云代金券