进程控制
task_struct结构体
1 | struct task_struct |
fork()
创建一个子进程
pid_t fork(void);
返回值:1.父进程的fork返回值为子进程的id
2.子进程的fork返回值为0(进程创建成功)
创建5个进程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//注意,第二个子进程还可以创建4个子进程
int main()
{
pid_t pid;
for(int i=0;i<10;i++)
{
pid=fork();
if(pid==-1){
perror("fork error:");
exit(1);
}
else if(pid==0){
printf("child %d =%u,parent id=%u\n",i,getpid(),getppid());
}
else {
printf("parent process=%u\n",getpid());
}
}
return 0;
}
进程共享
父子进程共享
父子进程之间遵循读时共享写时复制的原则,这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
共享:1、文件描述符。2、mmap建立的映射区(进程间通信)
测试:父子进程是否共享全局变量
调试gdb:指明gdb调试器跟踪哪个进程:
set follow-fork-mode child
set follow-fork-mode parent
孤儿进程&僵尸进程
孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程转为init进程,称为init进程领养的孤儿进程。
僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放在内核中,变成僵尸进程。回收子进程wait()与waitpid()
ps aux
wait()
父进程调用wait函数可以回收子进程,该函数有三个功能:1、阻塞等待进程退出。2、回收子进程残留资源。3、获取子进程结束原因,借助宏函数来 进一步判断进程终止的具体原因。
pid_t wait(int* status);
返回子进程id,或者-1(未成功)
waitpid()
作用同wait()函数,但是可以指定pid进程进行回收。
pid_t waitpid(pid_t pid,int* status,int options)
exec函数族
调用exec不会创建新的进程,一般fork一个子进程调用一个exec函数执行另外一个程序,子进程的数据区等会被另外一个程序完全替换。exec函数一旦调用成功即执行新的程序,不返回,只有失败才返回,错误值-1,所以通常我们直接在exec函数调用后直接调用perror()和exit(),无需if判断。
只有exevce是系统调用,其他几个函数最终都是调用的exevce(),所以execve在man手册第2节,其他函数在man手册第3节。
exevce()系统调用
int execve(const char *filename, char *const argv[], char *const envp[]);
execve()执行程序由 filename决定。
filename必须是一个二进制的可执行文件,或者是一个脚本以#!格式开头的解释器参数参数。如果是后者,这个解释器必须是一个可执行的有效的路径名,但是不是脚本本身,它将调用解释器作为文件名。
execlp()
加载一个进程,借助path环境变量。“ls”
int execlp(const char *file, const char *arg, ... (char *) NULL );
execl()
通过路径名来加载一个进程“/bin/ls”
int execl(const char path, const char arg, …(char *) NULL );
execvp()
加载一个进程,使用自定义环境变量env
int execvp(const char *file, char *const argv[]);
进程间通信(Inter-Process Communication)
管道(pipe)
管道是一种最基本的ipc机制,作用于有血缘关系 的进程之间,完成数据的传递,调用pipe系统函数即可创建一个管道,有如下特质:
1、一个管道是一个伪文件(实为内核缓冲区?)
2、由两个文件描述符引用,一个表示读端,一个表示写端。
3、规定数据从管道的写端流入管道,从读端流出。
4、使用简单。
管道的原理:管道实际上是内核使用环形队列机制,借助内核缓冲区(4K)[ulimit -a]实现。
管道的局限:
1、数据自己读不能自己写。
2、数据一旦被读走,便不在管道中存在,不可反复读取
3、由于管道采用双向半双工(单向)通信方式,因此数据只能在一个方向上流动
4、只能在有公共祖先的进程间使用管道pipe()
int pipe(int pipefd[2]);
pipefd会被赋两个文件描述符,无需open,但需要调用close(int fd),关闭一端,规定fd[0]->r,fd[1]->w,就像0对应标准输入,1对应标准输出一样,向管道文件读写数据其实就是在读写内核缓冲区,进程做一次fork,子进程和父进程各关闭一个读写端并各使用一个读写端进行通信。
FIFO(有名管道)
用于非血缘关系进程间通信,FIFO往往都是多个写进程,一个读进程。(服务器)
命令:mkfifo
函数:mkfifo()
int mkfifo(const char* pathname,mode_t mode);
参数:pathname是将要在文件系统中创建的一个专用文件。mode:用来规定FIFO的读写权限。Mkfifo函数如果调用成功的话,返回值为0;如果调用失败返回值为-1。
fifo打开后可以直接利用典型的输入输出函数从pathname文件中进行读写。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/*举例*/
int ret;
...
ret = mkfifo( "/tmp/cmd_pipe", 0666 );
if (ret == 0) ...{
// 成功建立命名管道
} else ...{
// 创建命名管道失败
/*读取数据*/
...
pfp = fopen( "/tmp/cmd_pipe", "r" );
...
ret = fgets( buffer, MAX_LINE, pfp );
...
}
非阻塞标志(O_NONBLOCK):
(1) 阻塞模式:只读open要阻塞到某个进程为写而打开此FIFO,只写open要阻塞到某个进程为读而打开此FIFO;
(2) 非阻塞模式:只读立即返回,如果没有进程为读而打开FIFO,则只写open返回-1,erron=ENXIO;
共享存储映射(mmap)
mmap()
void* mmap(void* addr,size_t length,int prot,int flags,int fd,off_t offset);
参数:
addr:建立映射区首地址,由linux内核指定,使用时直接传递NULL
length:申请创建映射区大小(ftruncate(),lseek()拓展文件大小)
prot:映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags:标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
MAP_SHARED:会将映射区所做的操作反映到物理设备(磁盘)上
MAP_PRIVATE:映射区所做的修改不会反映到物理设备上。
fd:用来建立映射区的文件描述符
offset:映射文件的偏移(4K的整数倍),只映射文件的一部分,产生映射空间
返回值:成功:返回创建的映射区首地址;失败:MAP_FAILED宏
匿名映射
因为每次创建映射区一定要依赖一个文件才能实现,通常为了建立映射区要open一个temp文件,创建好了在unlink()、close()掉、比较麻烦。为此可以直接使用匿名映射来替代,其实linux系统为我们提供了创建匿名映射的方法,无需依赖一个文件即可创建映射区,同样需要借助标志位参数flags来指定。
使用MAP_ANONYMOUS(或MAP_ANON),如:
int *=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
“4”这个参数就可以随意填写了,想要多大填多大。
munmap()
注意要释放映射空间
int munmap(void* addr,size_t length);
本地套接字
环境变量
打印所有环境变量1
2
3
4
5
6
7
8
9
10
11#include<iostream>
using namespace std;
extern char** environ;
int main()
{
int i;
for(int i=0;environ[i];i++)
{
printf("%s\n",environ[i]);
}
}
getenv()
获取环境变量
setenv()
设置环境变量