14.Linux实战开发2.0
1、进程
1、进程的概念
程序:存储在外部介质中的可执行文件
进程:进程是程序在某个数据集合上的一次运行活动,也是操作系统进行资源分配和保护的基本单位。是正在执行的程序,程序执行的具体实例(过程),一个程序可以执行多个进程。
2、进程的组成
1)进程控制块PCB:进程存在的唯一标识,是操作系统用来记录和刻画进程状态即环境信息的数据结构,也是操作系统掌握进程的唯一资料结构和管理进程的主要依据。当进程被创建的时候,操作系统为其创建PCB,当进程结束时,会回收其PCB。
-
进程描述信息
当进程被创建的时候,操作系统会为该进程分配一个唯一的、不重复的“身份证号”——PID(进程号)。进程号:全局唯一 0 ~ 32767 (2^16 ------>65536)
0号:调度进程
1号:初始化进程
进程描述信息还包括进程所属的用户ID(UID)
-
进程控制和管理信息
记录进程的运行情况。比如 CPU 的使用时间、磁盘使用情况、网络流量使用情况等。
-
资源分配清单
记录给进程分配了哪些资源。比如分配了多少内存、正在使用哪些 I/O 设备、正在使用哪些文件等。
-
CPU相关信息
进程在让出 CPU 时,必须保存该进程在 CPU 中的各种信息,比如各种寄存器的值。用于实现进程切换,确保这个进程再次运行的时候恢复 CPU 现场,从断点处继续执行。这就是所谓的保存现场信息。
2)数据段:运行过程中各种数据(比如程序中定义的变量)
3)程序段:程序的代码(指令序列)
3、进程的状态
1、就绪态:分配进程所需的内存,CPU等;
2、执行态:进程占有CPU正在运行;
3、等待态(挂起,睡眠,阻塞):进程不具备运行条件,正在等待某个事件的完成。
进程调度:就是从进程的就绪队列(阻塞)中按照一定的算法选择一个进程并将 CPU 分配给它运行 ,以实现进程的并发执行。
4、进程控制:
所谓进程控制就是对系统中的所有进程实施有效的管理,实现进程状态转换功能。包括创建进程、阻塞进程、唤醒进程、终止进程等,这些功能均由原语来实现,操作系统通过原语来完成进程原理,包括进程的同步和互斥、进程的通信和管理。
2、进程的几种特殊状态
僵尸进程:进程已经结束了,进程的资源没有被回收;
孤儿进程:父进程先执行结束,没有办法对子进程进行回收,子进程变成了一个孤儿进程;此时解决的办法是在启动的进程内找一个进程作为这些孤儿进程的父进程,或者直接让init
进程作为它们的父进程,进而释放孤儿进程占用的资源。
守护进程:运行在后台,回收孤儿进程,保护进程安全。
3、进程的创建
(1)、使用 fork()
函数:创建一个子进程;此时父进程和子进程成为两个独立的程序;
pid_t fork(void)
俩个返回值 pid
== 0 子进程
pid
>0 父进程
pid
<0 创建失败
父子进程的关系:父进程产生子进程;父进程要对子进程的资源进行回收;父子进程对cpu
的资源是竞争争夺的关系;子进程拷贝父进程的内存地址空间(包括地址和内存)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int num=0;
int main()
{
pid_t pid = fork();//创建进程
// printf("%d\n",getpid());//获取进程号
// printf("ppid : %d\n",getppid());//获取父进程号
// printf("pgid : %d\n",__getpgid(getpid()));//获取组进程号
if(pid < 0)//判断进程是否创建成功
{
perror("fork:");
}
if(pid == 0)//child
{
num ++;
printf("I am the child process!\n");
}
else//parent
{
printf("I am the parent process!\n");
}
printf("%d\n",num);
}
//输出结果
//I am the child process! 1
//I am the parent process! 0
多试几次发现有时候,先打印I am the parent process!
,有时会先打印I am the child process!
,是因为父子进程对cpu
的资源是竞争争夺的关系。而且打印的num
值也不相同,因为当父子进程中任意一个进程试图修改其中的数据时, 内核才会将要修改的数据所在的区域(页) 拷贝一份,所以父进程中的数值不变
(2)、使用 vfork()
函数创建进程
特点:1、子进程先动,即使使用sleep函数(这个函数会将进程暂时挂起)也是先执行子进程,然后再执行父进程。
2、子进程会在父进程的内存空间中运行,直到遇到exec函数或者exit函数为止
3、vfork
创建的子进程和父进程是共享地址空间的,而不是复制,因此子进程中的数据和父进程中的数据是共享的(这个共享会一直持续到子进程调用exec或者exit),如果子进程中修改了某个变量的值,那么在父进程中这个变量也将被修改。
#include "SystemHead.h"
int num = 0;
int main()
{
pid_t pid = vfork();//创建进程
if(pid < 0)//判断进程是否创建成功
{
perror("fork:");
}
if(pid == 0)
{
num++;//验证是否共享地址空间
printf("I am the child process ! num : %d\n",num);
sleep(3);//挂起,用于验证是否是子进程先动
}
else
{
printf("I am the parent process!num : %d\n",num);
}
return 0;
}
//输出结果:
//I am the child process ! num : 1
//I am the parent process! num : 1
3、进程的挂起:sleep
sleep()指线程被调用时,占着CPU不工作,形象的说明为“占着CPU”睡觉。它们会暂停执行一段时间,直到等待的时间结束才恢复执行或在这段时间内被中断。
4、进程的等待
函数等待的作用:
- 父进程在它的执行代码中调用进程等待的方法,等待子进程退出,防止子进程变成僵尸进程;
- 也就是说,进程等待是父进程调用某个接口进行等待,父进程等待子进程退出回收子进程的资源,防止子进程变成僵尸进程
等待的方法:wait
和 waitpid
1)、wait函数
进程一旦调用了wait,就会立刻阻塞自己,由wait分析当前进程中的某个子进程是否已经退出了,如果让它找到这样一个已经变成僵尸进程的子进程,wait会收集这个子进程的信息,并将它彻底销毁后返回;如果没有找到这样一个子进程,wait会一直阻塞直到有一个出现。
pid_t wait(int *__stat_loc)
参数:是一个指针类型,但是该指针类型并不是要传递一个指针参数,而是一个输出型参数;将wait函数内部计算的某个结果通过status变量返回给调用者;
2)、判断进程是否正常退出
-
WIFEXITED(status):判断子进程是否是正常退出
(((status) & 0x7f) == 0) &0x7f清理高位,判断低位;当正常退出的时候,信号7位全为0,异常退出的时候,>0
-
WEXITSTATUS: 当WIFEXITED返回非零值时,可以用这个宏来提取子进程的返回值
获取高8位状态返回值,真正的状态值存在高8位;所以需要右移8位
(((status) & 0xff00) >> 8)
3)、waitpid
函数
pid_t waitpid(pid_t __pid, int *__stat_loc, int __options)
参数:
-
pid
:pid
== -1,表示等待任意一个子进程,与wait等效
pid
>0,>0 等于传递了具体子进程的pid
,等待该指定进程 -
status:同wait用法一样
-
options:
-
0:阻塞状态:如果
waitpid
没有等到子进程退出,那么就一种陷入waitpid
函数内部 -
WNOHANG:非阻塞模式,不管子进程是否退出
--如果子进程退出,那么
waitpid
函数就是等待到子进程结束,然后执行其他语句后返回;–如果子进程没有退出,在该模式下waitpid
函数直接退出并返回,只是判断子进程是否退出。
-
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int num=0;
int main()
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork:");
}
if(pid == 0)
{
num ++;
printf("I am the child process!\n");
sleep(3);
exit(2);
}
else
{
int status = 0;
printf("I am the parent process!\n");
wait(&status);
waitpid(pid,&status,0);
if(WIFEXITED(status) != 0)
{
printf("child exit status is %d\n",WEXITSTATUS(status));
}
}
}
5、进程的退出:
exit( ):库函数:1、处理atexit()
注册的退出处理函数
2、关闭IO 缓冲区,关闭文件描述符
_exit( ):系统调用
atexit
:注册退出处理函数
atexit()
用来设置一个程序正常结束前调用的函数. 当程序通过调用exit()或从main 中返回时, 参数function 所指定的函数会先被调用, 然后才真正由exit()结束程序.返回值:如果执行成功则返回0, 否则返回-1, 失败原因存于errno
中.
#include "SystemHead.h"
void Func()
{
printf("malloc free!\n");
}
int main()
{
atexit(Func);
printf("main run !\n");
sleep(3);
return 0;
}
//输出结果
//main run !
//malloc free
6、exec函数族:在一个进程中执行另一个进程
带l的一类exac
函数(l表示list),包括execl
、execlp
、execle
,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。
1、execl()
函数
int execl(const char *path, const char *arg, ...);
参数说明:
第一参数path字符指针所指向要执行的文件路径, 接下来的参数代表执行该文件时传递的参数列表:argv[0],argv[1].
… 最后一个参数须用空指针NULL作结束。
函数返回值:
成功则不返回值, 失败返回-1, 失败原因存于errno
中,可通过perror()
打印;
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
execl("/bin/ls","ls","-l",NULL);//列出所有的命令行参数,以NULL结尾
return 0;
}
执行结果:
2、execlp()
函数
从环境变量中查找文件并执行
int execlp(const char * file, const char * arg, ...);
参数说明:
execlp()
会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0]、argv[1]
……, 最后一个参数必须用空指针(NULL)作结束。
返回值:
如果执行成功则函数不会返回, 执行失败则直接返回-1, 失败原因存于errno
中。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
execlp("ls","ls","-l",NULL);
return 0;
}//输出结果与execl相同
3、execle()
函数:
int execle(const char *__path, const char *__arg, ...)
可以传递一个指向环境字符串指针数组的指针,带e表示该函数取env[]
数组,而不使用当前环境。通过一个可执行文件test,执行excle
传入的环境变量,在一个函数调用的时候传入另一个函数中使用。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
printf("USER = %s\n",getenv("USER"));
printf("PASSWD = %s\n",getenv("PASSWD"));
return 0;
}
//编译生成一个test可执行文件
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
static char *env[] = {"USER=TEST","PASSWD=123456"};
execle("./test","test",NULL,env);
return 0;
}//执行结果
//USER = TEST
//PASSWD = 123456
带v不带l的一类exac
函数,包括execv、execvp、execve
,应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
4、execv
函数
#include "SystemHead.h"
int main()
{
char *value[] = {"ls","-l",NULL};
execv("/bin/ls",value);
return 0;
}
5、execvp
函数
#include "SystemHead.h"
int main()
{
char *value[] = {"ls","-l",NULL};
execvp("ls",value);
return 0;
}
6、execve
函数
#include "SystemHead.h"
int main()
{
char *env[] = {"USER=TEST","PASSWD=123456"};
execve("./test",value,env);
return 0;
}
7、system函数
1、函数声明
int system(const char *__command)
2、函数作用
system()函数主要用于发出一个DOS命令,该函数已经收录在标准c库中,可以直接调用
3、具体使用
#include "SystemHead.h"
int main()
{
char temp[20] = {0};
char name[10] = {0};
scanf("%s",name);
//sprintf(temp,"mkdir %s",name);
// system("cp test.c y.txt");
sprintf(temp,"rm -rf %s",name);
system(temp);
return 0;
}