0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux进程控制简介与要素及相关函数详解

GReq_mcu168 来源:CSDN技术社区 作者:码农爱学习 2021-04-03 10:52 次阅读

Linux进程简介

进程是操作系统中的一个重要概念,它是一个程序的一次执行过程,程序是进程的一种静态描述,系统中运行的每一个程序都是在它的进程中运行的。

进程4要素

要有一段程序供该进程运行

进程专用的系统堆栈空间

进程控制块(PCB),具体实现是task_struct结构

有独立的存储空间

Linux系统中所有的进程是相互联系的,除了初始化进程外,所有进程都有一个父进程。新的进程不是被创建,而是被复制,或是从以前的进程复制而来。Linux中所有的进程都是由一个进程号为1的init进程衍生而来的。

Linux系统包括3种不同类型的进程,每种进程都有自己的特点和属性:

交互进程:由一个Shell启动的进程,既可以在前台运行,又可以在后台运行

批处理进程:这种进程和终端没有联系,是一个进程序列

监控进程(守护进程):Linux启动时启动的进程,并在后台运行

进程控制块

在Linux中,每个进程在创建时都会被分配一个数据结构,称为进程控制块(PCB, Process Control Block),描述进程的运动变化过程,与进程是一一对应的关系。通常PCB包含以下信息

进程标识符:每个进程的唯一标识符,可以是字符串,也可以是数字。

进程当前状态:为方便管理,相同状态的进程会组成一个队列,如就绪进程队列。

进程相应的程序和数据地址:以便把PCB与其程序和数据联系起来。

进程资源清单:列出所有除CPU外的资源记录,如拥有的I/O设备,打开的文件列表等。

进程优先级:反映进程的紧迫程度,通常由用户指定和系统设置。

CPU现场保护区:当进程因某种原因不能继续占用CPU时,释放CPU,需要将CPU的各种状态信息保护起来。

进程同步与通信机制:用于实现进程间互斥、同步和通信所需的信号量等。

进程所在队列PCB的链接字:根据进程所处的现行状态,进程相应的PCB参加到不同队列中,PCB链接字指出该进程所在队列中下一进程PCB的首地址。

与进程有关的其它信息:如进程记账信息,进程占用CPU的时间等。

通过ps命令可以查看系统中目前有多少进程正常运行

通过ps-aux命令可以查看每个进程的详细信息

进程控制的相关函数

fork()函数

系统调用fork()函数派生一个进程,函数原型为:

#include 《sys/types.h》

#include 《unistd.h》

pid_t fork(void);

运行成功,父进程返回子进程ID,子进程饭0;运行出错返回-1。

fork系统调用的作用是复制一个进程,从而出现两个几乎一样的进程。一般来说,fork后是父进程先执行还是子进程先执行是不确定的,取决于内核所实使用的调度算法

fork函数示例,fork_test.c:

#include 《sys/types.h》

#include 《stdio.h》

#include 《stdlib.h》

#include 《unistd.h》

int main(void)

{

int count = 0;

pid_t pid;

pid = fork();

if(pid 《 0)

{

printf(“error in fork!”);

exit(1);

}

else if(pid == 0)

printf(“I am the child process, the count is %d, my process ID is%d

”, count, getpid());

else

printf(“I am the parent process, the count is %d, my process ID is%d

”, ++count, getpid());

return 0;

}

编译后运行:

$ 。/fork_test

I am the parent process, the count is 1, my process ID is2308

I am the child process, the count is 0, my process ID is2309

在语句pid = fork();之前,只有一个进程在执行代码,但在该语句之后,有两个进程在执行之后的代码,根据pid的不同执行不同的语句。

fork调用的神奇之处在于被调用一次,能够返回两次,返回结果可能有3种情况:

父进程中:fork返回新创建的子进程的ID

子进程中:fork返回0

出现错误:fork返回负值

fork出错的原因有2:

当前进程数已达系统规定的上限,此时errno的值被设置为EAGAIN

系统内存不足,此时errno的值被设置为ENOMEN

errno是Linux下的一个宏定义常量,当Linux中C API函数发生异常时,一般会将errno变量赋值为一个正整数(需include),不同的值表示不同的含义,通过查看该值可推测出错原因。

vfork()函数

vfork()与fork()的区别是:fork()需要复制父进程的数据段,而vfork()不需要完全复制,在子进程调用exec()或exit()之前,子进程与父进程共享数据段。fork()不对父子进程的执行次序作限制,而vfork()调用后,子进程先运行,父进程挂起,直到子进程调用了exec()或exit()后,父子进程的执行次序才不再有限制。

实际上,vfork()创建出的不是真正意义的进程,它缺少了进程4要素的最后一项——独立的内存资源。

vfork()创建父子进程共享数据段测试,vfork_test1.c():

#include 《sys/types.h》

#include 《stdio.h》

#include 《stdlib.h》

#include 《unistd.h》

int main(void)

{

int count = 1;

int child;

printf(“Before create son, the father‘s count is:%d

”, count);

child = vfork();

if(child 《 0)

{

printf(“error in vfork!”);

exit(1);

}

if(child == 0)

{

printf(“This is son, his pid is:%d and the count is:%d

”, getpid(),++ count);

exit(1);

}

else

printf(“After son, This is father, his pid is:%d and the count is:%d, and the child is:%d

”, getpid(), count, child);

return 0;

}

编译后运行:

$ 。/vfork_test1

Before create son, the father’s count is:1

This is son, his pid is:2530 and the count is:2

After son, This is father, his pid is:2529 and the count is:2, and the child is:2530

可以看出,在子进程中修改了count的值,变为2,而父进程中count值也为2,说明父子进程共享count,即父子进程共享内存区。

vfork()创建子进程导致父进程挂起测试,vfork_test2():

#include 《sys/types.h》

#include 《stdio.h》

#include 《stdlib.h》

#include 《unistd.h》

int main(void)

{

int count = 1;

int child;

printf(“Before create son, the father‘s count is:%d

”, count);

if(!(child = vfork()))

{

int i;

for(i=0; i《100; i++)

{

printf(“This is son, the i is:%d

”, i);

if(i == 70)

exit(1);

}

printf(“This is son, his pid is:%d and the count is:%d

”, getpid(), ++count);

exit(1);

}

else

printf(“After son, This is father, his pid is:%d and the count is:%d, and the child is:%d

”, getpid(), count, child);

return 0;

}

编译后运行:

$ 。/vfork_test2

Before create son, the father’s count is:1

This is son, the i is:0

This is son, the i is:1

This is son, the i is:2

。..省略

This is son, the i is:67

This is son, the i is:68

This is son, the i is:69

This is son, the i is:70

After son, This is father, his pid is:2541 and the count is:1, and the child is:2542

可以看出,父进程是等待子进程执行完毕后才开始执行。

exec函数族

Linux使用exec函数族来执行新的程序,以新的子进程来完全代替原有的进程,exec函数族包含6个函数:

#include 《unistd.h》

int execl(const char *pathname, const char *arg, 。..);

int execlp(const char *filename, const char *arg, 。..);

int execle(const char *pathname, const char *arg, 。.., char *const envp[]);

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

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

int execve(const char *pathname, char *const argv[], char *const envp[]);

运行成功无返回,出错返回-1。

函数中含义字母l的:其参数个数不定,参数由命令行参数列表组成,最v后一个NULL表示结束。

函数中含义字母v的:使用一个字符串数组指针argv指向参数列表,与含字母l的函数参数列表完全相同。

函数中含义字母p的:可以自动在环境变量PATH指定的路径中搜索要执行的程序,其第一参数filename为可执行函数的文件名,注意其它函数的第一个参数pathname为路径名

函数中含义字母e的:比其它函数多了一个字符串指针型的envp参数,用于指定环境变量。

实际上,只有execve()函数才是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。与一般情况不同,exec函数族执行成功后不会返回,因为调用进程实体,包括代码段、数据段和堆栈段都被新的内容取代,只是进程ID等一些表面上的信息仍保持原样。

exec函数族使用举例,exec_example.c:

#include 《unistd.h》

#include 《stdio.h》

int main(void)

{

char *envp[] = {“PATH=/tmp”, “USER=root”, “STATUS=testing”, NULL};

char *argv_execv[] = {“echo”, “excuted by execv”, NULL};

char *argv_execvp[] = {“echo”, “excuted by execvp”, NULL};

char *argv_execve[] = {“env”, NULL};

if(fork()==0)

{

if(execl(“/bin/echo”, “echo”, “executed by execl”, NULL))

perror(“Err on execl”);

}

if(fork()==0)

{

if(execlp(“echo”, “echo”, “executed by execlp”, NULL))

perror(“Err on execlp”);

}

if(fork()==0)

{

if(execle(“/usr/bin/env”, “env”, NULL, envp))

perror(“Err on execle”);

}

if(fork()==0)

{

if(execv(“/bin/echo”, argv_execv))

perror(“Err on execv”);

}

if(fork()==0)

{

if(execvp(“echo”, argv_execvp))

perror(“Err on execvp”);

}

if(fork()==0)

{

if(execve(“/usr/bin/env”, argv_execve, envp))

perror(“Err on execve”);

}

return 0;

}

上述程序用到了perror()函数,它用来将函数发生错误的原因输出到标准输出(stderr),其函数原型为:

《pre class=“lang_c”》#include 《stdio.h》

void perror(const char *s)

```

编译后执行:

$ 。/exec_example

PATH=/tmp

USER=root

STATUS=testing

executed by execl

executed by execlp

$ PATH=/tmp

USER=root

STATUS=testing

excuted by execvp

excuted by execv

由于各子进程执行的顺序无法控制,因而每次运行结果的输出顺序会有不同。

使用exec函数族,一般要加上错误判断语句,因为exec函数易由多种原因运行失败:

找不到文件或路径:errno被设置为ENOENT

数组argv和envp忘记使用NULL结束:errno被设置为EFAULT

没有文件的运行权限:errno被设置为EACCES

exit()与_exit()函数

这两个函数都是用于终止进程,其定义分别为:

#include 《stdlib.h》

void exit(int status);

#include 《unistd.h》

void _exit(int status);

两者主要区别在于:

定义及所需头文件不同

_exit()立即进入内核;exit()则先执行一些清除处理(包括调用执行个终止处理程序,关闭所有标准I/O流等),然后进入内核。

exit()在调用之前要检查文件的打开情况,把文件缓冲区的内容写回文件;_exit()则直接使进程停止,清除其使用的内存空间,并销毁其在内核中的各种数据结构。

在Linux的标准函数库中,有一套被称为“高级I/O的函数”,如printf()、fopen()等,也被称为“缓冲I/O(buffered I/O)”,其特征是对应每一个打开的文件,在内存中都有一片缓冲区,每次会多读出若干条记录,当达到一定的条件(如达到一定数量,或遇到特定字符,如‘ ’和文件结束符EOF)时,再将缓冲区的内容一次性写入文件,从而增加读写速度。但是,这种情况下,如果使用_exit()退出,会导致某些数据未被保存,而用exit()则不会有问题。

exit()与_exit()函数的区别测试,exit_differ.c:

#include 《sys/types.h》

#include 《stdio.h》

#include 《stdlib.h》

#include 《unistd.h》

int main(void)

{

pid_t pid;

if((pid=fork()) == -1)

{

printf(“failed to create a new process

”);

exit(0);

}

else if(pid == 0)

{

printf(“

child process, output begin

”);

printf(“child process, content in buffer”);

_exit(0);

}

else

{

printf(“parent process, output begin

”);

printf(“parent process, content in buffer”);

exit(0);

}

return 0;

}

编译后执行:

$ 。/exit_differ

parent process, output begin

parent process, content in buffer

child process, output begin

由于printf函数遇到’ ‘时才从缓冲区读取数据,在子进程中,因为_exit(0)直接将缓冲区的内容清除了,内容没有显示;而父进程中,执行exit(0)之前会先将缓冲区的内容显示出来。

wait()与waitpid()函数

在一个进程调用了exit()之后,该进程并非立即消失,而是留下一个僵尸进程(Zombie)的数据结构,这时的一种处理方法就是使用wait()和waitpid()函数。

僵尸态是进程的一种特殊状态,没有任何可执行代码,也不能被调度,仅仅在进程中保留一个位置,记载改进程的退出状态等信息供其它进程收集。

wait()和waitpid()函数原型:

#include 《sys/types.h》

#include 《sys/wait.h》

pid_t wait(int *status);

pid_t waitpid(pid_t, int *status, int options);

运行成功返回进程ID,出错返回-1。

参数status用于保存进程退出时的一些状态,如果只是想把进程灭掉,可以设置该参数为NULL。

参数pid用于指定所等待的线程。

pid取值含义

pid 》 0只等待进程ID为pid的子线程

pid = -1等待任何一个子线程,此时waitpid等价于wait

pid = 0等待同一个进程组中的任何子进程

pid 《 -1等待一个指定进程组中的任何子进程,其进程ID为pid的绝对值

参数options提供一些额外的选项来控制waitpid,包括WNOHANG和WUNTRACED两个选项,这是两个常数,可以用|运算符连接使用。其中WNOHANG参数用于设置不等待子进程退出,立即返回,此时waitpid返回0;WUNTRACED参数用于配置跟踪调试。

进程一旦调用wait后,就立刻阻塞自己,如果当前进程的某个子进程已退出,则收集其信息,否则wait会一种阻塞在这里,直到有一个僵死进程出现。

wait()示例

wait_example.c:

#include 《sys/types.h》

#include 《sys/wait.h》

#include 《stdio.h》

#include 《stdlib.h》

#include 《unistd.h》

int main(void)

{

pid_t pc, pr;

if((pc = fork()) 《 0)

{

printf(“error in fork!”);

exit(1);

}

else if(pc == 0)

{

printf(“This is child process with pid of %d

”, getpid());

sleep(10);

}

else

{

pr = wait(NULL);

printf(“I catched a child process with pid of %d

”, pr);

}

exit(0);

}

编译后执行:

$ 。/wait_example

This is child process with pid of 10093

I catched a child process with pid of 10093

可以看到,第1行输出后,等待大约10秒,第2行才输出,这10秒就是子线程的睡眠时间。

waitpid()示例

父进程和子进程分别睡眠10秒钟和1秒钟,代表所作的相应工作。父进程利用工作的简短间歇查看子进程是否退出,如果退出就收集它。waitpid_example.c:

#include 《sys/types.h》

#include 《sys/wait.h》

#include 《stdio.h》

#include 《stdlib.h》

#include 《unistd.h》

int main(void)

{

pid_t pc, pr;

if((pc = fork()) == -1)

{

printf(“failed to create a new process”);

exit(0);

}

else if(pc == 0)

{

sleep(10);

exit(0);

}

do

{

pr = waitpid(pc, NULL, WNOHANG);

if(pr == 0)

{

printf(“No chiled exited

”);

sleep(1);

}

}while(pr == 0);

if(pr == pc)

printf(“successfully get child %d

”, pr);

else

printf(“some error occured

”);

return 0;

}

sdfgh

$ 。/waitpid_example

No chiled exited

No chiled exited

No chiled exited

No chiled exited

No chiled exited

No chiled exited

No chiled exited

No chiled exited

No chiled exited

No chiled exited

successfully get child 2711

可以看到,父进程经过10次失败尝试后,终于收集到了退出的子进程。

获取子进程返回状态

对于wait()和waitpid()中的status参数,当其值不为NULL时,子进程的退出状态会以int值的形式保存其中,通过一套专门的宏(macro)可以读取存入的状态值,这里只列举两个常用的宏:

宏定义含义

WIFEXITED(status)子进程正常退出时,返回一个非零值,否则返回零

WEXITSTATUS(status)当WIFEXITED为真时,此宏才可用,返回该进程退出的代码

示例,子进程调用exit(3)退出,WIFEXITED(status)指示子进程正常退出,WEXITSTATUS(status)就会返回3。get_status.c:

#include 《sys/types.h》

#include 《sys/wait.h》

#include 《stdio.h》

#include 《stdlib.h》

#include 《unistd.h》

int main(void)

{

int status;

pid_t pc, pr;

if((pc = fork()) 《 0)

{

printf(“error in fork!”);

exit(1);

}

else if(pc == 0)

{

printf(“This is child process with pid of %d.

”, getpid());

exit(3);

}

else

{

pr = wait(&status);

if(WIFEXITED(status))

{

printf(“the child process %d exit normally.

”, pr);

printf(“the return code is %d.

”, WEXITSTATUS(status));

}

else

printf(“the child process %d exit abnormally.

”, pr);

}

return 0;

}

assvf

$ 。/get_status

This is child process with pid of 2718.

the child process 2718 exit normally.

the return code is 3.

可以看出,父进程捕捉到了子进程的返回值3。

system()函数

函数原型:

#include 《stdlib.h》

int system(const char *cmdstring);

sysytem()调用fork()产生子进程,由子进程来调用/bin/sh-cmdstring来执行参数cmdstring字符串所代表的命令,此命令执行完后随即返回原调用的进程。

编程示例,4次调用system,设置不同的命令行参数,system返回不同的结果,cmd_system.c:

#include 《stdio.h》

#include 《stdlib.h》

int main(void)

{

int status;

if((status = system(NULL)) 《 0)

{

printf(“system error!

”);

exit(0);

}

printf(“exit status=%d

”, status);

if((status = system(“date”)) 《 0)

{

printf(“system error!

”);

exit(0);

}

printf(“exit status=%d

”, status);

if((status = system(“invalidcommand”)) 《 0)

{

printf(“system error!

”);

exit(0);

}

printf(“exit status=%d

”, status);

if((status = system(“who; exit 44”)) 《 0)

{

printf(“system error!

”);

exit(0);

}

printf(“exit status=%d

”, status);

return 0;

}

adss

$ 。/cmd_system

exit status=1

2019年 12月 10日 星期二 1436 CST

exit status=0

sh: 1: invalidcommand: not found

exit status=32512

deeplearning pts/0 2019-12-10 13:46 (192.168.1.110)

exit status=11264

第1次调用system,参数为NULL,返回结果为1,说明在本Linux系统下system可用;第2次调用system,参数为data,system成功执行;第3次调用system,参数为一个非法的字符串命令,返回结果shell的终止状态(命令出错)32512;第4次调用system,参数为who,显示登录用户情况,exit 44是退出当前的shell,system成功返回,返回值11264。

参考:《精通Linux C编程》- 程国钢
编辑:lyn

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10986

    浏览量

    206711
  • 函数
    +关注

    关注

    3

    文章

    3863

    浏览量

    61303
  • 编译
    +关注

    关注

    0

    文章

    613

    浏览量

    32371

原文标题:Linux进程控制

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    OpenHarmony中SELinux使用详解

    ,旨在增强传统Linux操作系统的安全性,解决传统Linux系统中自主访问控制(DAC)系统中的各种权限问题(如root权限过高等)。这里举一个例子便于理解,假设系统中某个服务进程出现
    发表于 04-03 10:43

    浅谈Linux进程

    进程和程序的区别: 进程是动态的,程序是静态的 一、进程的创建(fork()函数) int main(){ pid_t pid; pid=fork(); if(pid
    的头像 发表于 01-28 15:54 93次阅读
    浅谈<b class='flag-5'>Linux</b>的<b class='flag-5'>进程</b>

    linux查看weblogic进程

    Linux操作系统中,WebLogic是一种常用的Java应用服务器,用于部署和管理企业级Java应用程序。为了确保WebLogic服务器正常运行,有时我们需要查看WebLogic进程以了解其状态
    的头像 发表于 12-05 16:07 706次阅读

    为什么需要进程 特征和定义有哪些

    资源分配调度的独立单位。 结构特征:为了使程序能够独立运行,应配置一个进程控制块PCB。进程是由程序段,相关的数据段和PCB(进程控制块)三部分构成的。 动态性:
    的头像 发表于 10-08 15:29 368次阅读
    为什么需要<b class='flag-5'>进程</b> 特征和定义有哪些

    STM32库函数SystemInit()详解

    STM32库函数SystemInit()详解
    的头像 发表于 09-18 15:45 1947次阅读
    STM32库<b class='flag-5'>函数</b>SystemInit()<b class='flag-5'>详解</b>

    Linux进程相关知识

    进程是在你的系统上运行的程序。它们由内核管理,每个进程都有一个与之关联的ID,称为进程ID(PID)。这个PID是按照进程创建的顺序分配的。
    发表于 08-09 10:02 211次阅读
    <b class='flag-5'>Linux</b>下<b class='flag-5'>进程</b><b class='flag-5'>相关</b>知识

    Arch Linux RISC-V 端口及相关作品简介

    Arch Linux RISC-V 端口及相关作品简介 演讲ppt分享
    发表于 07-17 16:34 3次下载

    linux操作系统中的进程创建和销毁函数解析

    第一次遇见创建进程是在Linux启动流程中,reset_init函数调用kernel_thread函数创建了2个内核进程:kernel_in
    发表于 06-26 09:12 409次阅读
    <b class='flag-5'>linux</b>操作系统中的<b class='flag-5'>进程</b>创建和销毁<b class='flag-5'>函数</b>解析

    Linux LED子系统详解

    Linux LED子系统详解
    的头像 发表于 06-10 10:37 977次阅读
    <b class='flag-5'>Linux</b> LED子系统<b class='flag-5'>详解</b>

    Linux进程的睡眠和唤醒

    Linux中,仅等待CPU时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为 TASK_RUNNING。一旦一个运行中的
    发表于 06-07 12:26 279次阅读

    深度剖析Linux进程控制(下)

    Linux中,fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子
    的头像 发表于 05-12 10:49 320次阅读
    深度剖析<b class='flag-5'>Linux</b>中<b class='flag-5'>进程控制</b>(下)

    深度剖析Linux进程控制(上)

    Linux中,fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子
    的头像 发表于 05-12 10:49 324次阅读
    深度剖析<b class='flag-5'>Linux</b>中<b class='flag-5'>进程控制</b>(上)

    Linux内核进程管理与调度:策略优化与实践分析

    一个与之相关的优先级,如果有多个可执行的进程等待CPU资源,那么具有更高优先级的进程将优先被调度执行。今天就给大家讲解一下Linux内核中的进程
    发表于 05-08 09:42 592次阅读
    <b class='flag-5'>Linux</b>内核<b class='flag-5'>进程</b>管理与调度:策略优化与实践分析

    Linux进程间如何实现共享内存通信

    这次我们来讲一下Linux进程通信中重要的通信方式:共享内存作为Linux软件开发攻城狮,进程间通信是必须熟练掌握的重要技能,而共享内存是在程序开发中常用的也是重要的一种
    发表于 04-26 17:14 571次阅读

    OpenHarmony应用模型的构成要素分析

    。 OpenHarmony应用模型的构成要素包括:应用组件、应用进程模型、应用线程模型、应用任务管理模型、应用配置文件五个部分。 1.应用组件 应用组件是应用的基本组成单位,是应用的运行入口。用户启动
    发表于 04-24 10:26