进程
进程是一个可并发执行的具有独立功能的程序关于某个数据集合的一次执行过程,也是操作系统执行资源分配和保护的基本单位。程序的一次执行就是一个进程一个程序可以派生多个进程多个不同程序运行的时候,也会有多个相对应的进程与其相互对应进程是动态的,有始有终,有自己的生命周期,有进程状态的变化程序与进程的区别
程序是静止的,无生命的,进程是活动的程序是可以脱离机器长期存在,而进程是执行了的程序程序不具备并发性,不占据系统的资源,进程则相反,具备并发性、会占据内存空间,并且会受到其他进程的制约和影响一个程序对应很多的进程进程的状态
运行状态就绪状态 缺少cpu等待状态 缺少IO资源,等待相关命令
使用命令 ps -ax 查看进程的相关状态;比如进程号 、 进程的状态、进程树的形成
计算机启动之后,BIOS从磁盘引导扇区加载系统引导程序,它将Linux系统装入内存,并且跳到内核处开始执行,Linux内核就执行初始化工作:初始化硬件、初始化内部数据结构、建立进程0进程0创建进程1,进程1是以后所有创建进程的祖先,它负责初始化所有的用户进程。进程1创建shell,shell进程显示提示符,等待命令的输入init进程ID为1,通常是init进程,在自检过程结束的时候由内核调用init进程不会终止init进程是一个普通的用户进程(与交换进程不同,他不是内核中的系统进程,但是它以超级用户特权执行)进程的创建
任何进程的创建,都是基于现有的进程进程的创建可以使用fork 和 execfork 视为新的进程分配响应的数据结构,并且将父进程的相应上下文信息复制过来exec 将可执行文件的正文和数据转入内存替代先前的内容(从父进程复制过来的数据),并开始执行正文段,将当前进程替换为一个新的进程进程的终止
使用exit()exit释放除了task_struct以外的所有的上下文,父进程收到子进程的终结信号之后,释放子进程的task_structvfork调用之后,将立即调用exec,这样就不需要拷贝父进程的所有的页表,因此比fork快pid_t fork(void);pid_t vfor(void);头文件 unistd.hfork系统调用
当fork函数调用成功时,对父进程返回子进程的PID,对子进程返回0调用失败的时候,父进程返回-1,并没有子进程的创建fork函数返回 两个参数,分别给父进程和子进程pid_t new_pid;new_pid = fork();switch(new_pid){case -1://Error!break;case 0://Child processbreak;default://parent processbreak;}
exec系统调用
exec是用来执行一个可执行文件来代替当前进程的执行镜像int execl(const char * path,const char * arg, ...)int execlp(const char * file,const char * arg, ...)int execle(const char *path, const char * arg,...,const char *envp[])int execv(const char *path,const char * argv[])int execve(const char * filename,char * const argv[],char * const envp[])int execvp(const char *file,char * const argv[])函数的原型
int execve(const char * filename,char * const argv[],char * const envp[])filename 执行的文件argv传递给文件的参数envp 传递给文件的环境变量当参数path所指定的文件替换原进程的执行镜像之后,文件的path开始执行,参数argv和envp便传递给进程l 表示listv表示vectore 可以传递新进程环境变量 execle execvep 可执行文件查找方式为文件的名字 execlp execvpchar* envp[] = {"PATH = /tmp","User = lei","STATUS = testing", nullptr};char* argv_execv[] = {"echo","executed by execv", nullptr};char* argv_execvp[] = {"echo","executed by execvp", nullptr};char* argv_execve[] = {"env", nullptr};if (execl("/bin/echo","echo","executed by execl", nullptr)<0)perror("Err on execl");if (execlp("echo","echo","executed by execlp", nullptr)<0)perror("Err on execlp");if(execle("/user/bin/env","env", nullptr,envp)<0)perror("Err on execle");if (execv("/bin/echo",argv_execv)<0)perror("Err on execv");if (execvp("echo",argv_execvp))perror("Err on execvp");if(execve("/user/bin/ebv",argv_execve,envp)<0)perror("Err on execve");
函数
头文件 <sys/types.h> <unistd.h>pid_t getpid(void);返回调用进程的进程IDpid_t getppid(void);但会调用进程的父进程IDuid_t getuid(void);返回调用进程的实际用户IDuid_t geteuid(void);返回进程的有效用户IDgid_t getgid(void);返回进程的实际组IDgid_t getegit(void);调用进程的有效组ID#include <iostream>#include <sys/types.h>#include <unistd.h>int main(){pid_t cld_pid;cld_pid = fork();if (cld_pid == 0){std::cout << "This is child process.\n" << std::endl;printf("My PID(child) is %d\n",getpid());printf("My child PID is %d\n",cld_pid );}else{std::cout << "This is parent process.\n" << std::endl;printf("My PID(parent) is %d\n",getpid());printf("My child PID is %d\n",cld_pid );}return 0;}
exit系统调用
_exit 直接结束进程,清除其内存使用空间,并且清除其在内核中的数据结构exit 函数在调用之前会检查文件的打开情况,把文件的缓存区的内容写会文件中,比如调用printf()函数wait系统调用
wait函数 用于使父进程阻塞,直到一个进程结束或者该进程收到一个指定信号为止调用wait或者waitpid的进程可能会:阻塞:如果其所有的进程还在运行带子进程的终止状态立即返回(如果一个子进程已经终止,正等待父进程存取其终止状态)出错立即返回(如果没有任何子进程)pid_t wait(int *status); status返回子进程退出时的状态信息pid_t waitpid(pid_t pid,int *status,int options);//等待指定的进程id头文件 <sys/types.h> 和 <sys/wait.h>两个函数返回数值:如果成功返回子进程的ID号,如果出现问题,返回-1wait和waitpid函数的区别
在一个子进程终止之前,wait使其调用者阻塞,而waitpid有一个选项,可以不阻塞调用者waitpid并不等待第一个终止的子进程,他有若干个选择项,可以控制他所等待的特定进程实际上,wait函数是waitpid的一个特例守护进程
创建子进程,然后父进程退出在子进程中创建新的会话 setsid();改变目录为根目录 chdir("/");重设文件的权限掩码umask(0);关闭文件描述符 for(int i = 0;i < MAXFILE; i++){close(i);}守护进程的工作 while(1){}#include <iostream>#include <unistd.h>#include <ftw.h>#include <fcntl.h>int main(){pid_t pc;char *buf = "This is a Daemon\n";int len = strlen(buf);pc = fork();if (pc < 0){printf("Error fork!\n");exit(-1);} else if (pc > 0){exit(0);}setsid();//在子进程中创建新的会话chdir("/");//改变目录为根目录umask(0);//重设文件的权限掩码for (int i = 0; i < 65535; ++i) {close(i);}int fd;while (1){if (fd = open("/tmp/daemon.log",O_CREAT|O_WRONLY|O_APPEND,0600) < 0){perror("open");exit(1);}write(fd,buf,len+1);close(fd);sleep(10);}return 0;}
sleep函数
函数使用sleep用来指定进程挂起来的指定的秒数。该函数的调用格式如下unsigned int sleep(unsigned int seconds);头文件 unistd.h进程间通信
数据传输:一个进程将 数据 发送给另外一个进程,数据大小一般是一个字节到几兆字节之间共享数据:多个进程之间操作共享数据,一个进程对共享数据的修改,另外的进程可以看到通知事件:一个进程向另外一个进程发送消息,通知它发生了某件事情。(如:子进程终止,通知父进程)资源共享:多个进程之间共享相同的资源,需要内核提供 锁 和 同步机制进程控制:有些进程完全控制另一个进程的执行(如Debug),这个时候,进程会拦截另外一个进程所有的陷入(陷入内核)和异常,并且及时知道他的状态改变进程间通信 (IPC)Linux进程间通信 包括Unix进程间通信、Socket进程间通信、POSIX进程间通信、System V进程间通信Unix进程间通信 -> 管道 FIFO 信号System V进程间通信 ->System V消息队列System V信号灯System V共享内存POSIX进程间通信 -> posix消息队列posix信号灯posix共享内存现在的linux使用的进程间通信方式
管道(pipe) 和 有名管道(FIFO)信号(signal):比较复杂的通信方式,用于通知其余进程,信号可以用于进程之间通信外,信号还可以发送信号给进程本身。消息队列:消息的链接表,包括posix消息队列和system V消息队列;有权限的进程可以读写消息队列;克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺陷共享内存:多个进程访问同一块内存空间,是最快的IPC方式,往往结合信号量来实现进程间同步以及互斥信号量 (使用不多)套接字(socket) (网络)管道和有名管道的区别
管道:具有亲缘关系的进程间通信,需要通信双方的进程有共同的祖先进程有名管道:允许无亲缘关系的管道之间通信,在文件系统中有对应的文件名字;有名管道通过命令 mkfifo或系统调用mkfifo来创建具体使用
管道
linux命令允许重定向,重定向就是管道 例如 ls > 1.txt管道是单向的、先进先出的,无结构的、固定大小的字节流,他把一个进程的标准输出和另外一个进程的标准输入连接在一起写进程在管道的末尾输入数据,读进程在管道的首端读取数据。数据被读出后会从管道中移除,其他进程对此不可见了管道提供了简单的流控制机制。进程试图读取空的管道时,在有数据填充管道之前会处于阻塞状态;同理,管道数据已满时,未有读进程读取数据,写进程往管道里面写入数据会处于阻塞状态管道用于进程之间通信使用系统调用pipe() 函数,创建一个简单的管道,只需接受一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符创建一个管道之后,一般情况下进程将会产生一个新的进程系统调用pip() 原型:int pipe(int fd[2])返回值:成功返回0 ;失败返回 -1errno = EMFILE 没有空闲的文件描述符errno = ENFILE 系统文件表已满errno = EFAULT fd数组无效代码
#include <stdio.h>#include <unistd.h>int main(void){int pipe_fd[2];if(pipe(pipe_fd) < 0){printf("pipe create error!\n");return -1;} else{printf("pipe create success!\n");}close(pipe_fd[0]);close(pipe_fd[1]);return 0;}
先创建一个管道 再创建一个子进程
父子进程之间通信 父进程写入;子进程读入数据
当读到10的时候,结束进程#include <stdio.h>#include <unistd.h>#include <sys/wait.h>int main(void){int pipe_fd[2];pid_t pid;char buf[2]={0};//在创建子进程之前创建管道if(pipe(pipe_fd) < 0){printf("pipe create error!\n");_exit(-1);} else{printf("pipe create success!\n");printf("pipe_fd[0] = %d,pipe_fd[1] = %d",pipe_fd[0],pipe_fd[1]);}pid = fork();//创建子进程//子进程执行的代码if (pid == 0){close(pipe_fd[1]);while (1){if (read(pipe_fd[0],buf,1) > 0){//if (buf[0] % 3 == 0 && buf[0] != 0)printf("%d\n",buf[0]);if (buf[0] == 10)_exit(0);}}close(pipe_fd[0]);} else{//父进程执行的代码close(pipe_fd[0]);for (buf[0]=0;buf[0]<=10;buf[0]++) {write(pipe_fd[1],buf,1);}close(pipe_fd[1]);wait(nullptr);}return 0;}
上面这个例子 感觉有问题
#include <stdio.h>#include <unistd.h>#include <wait.h>#include <cstring>int main(){int pipe_fd[2];if (pipe(pipe_fd) < 0){printf("pipe create error!\n");_exit(-1);return -1;} else{printf("pipe create success!\n");printf("pipe_fd[0] = %d,pipe_fd[1] = %d",pipe_fd[0],pipe_fd[1]);}pid_t pid = fork();// char buf[2] = {0};char buf[100] ={"Hello world"};int len = strlen(buf);if (pid == 0){close(pipe_fd[1]);while(1){if (read(pipe_fd[0],buf,len) > 0){printf("%s\n",buf);}}close(pipe_fd[0]);} else{//父进程执行此段代码close(pipe_fd[0]);for (int i = 0;i<10;i++){write(pipe_fd[1], buf, len);}close(pipe_fd[1]);wait(nullptr);}return 0;}
简单修改如下
通过打开两根管道,实现一个双向的管道,但是需要在子进程中正确的设置文件的描述符号必须在fork之前使用pipe(),否则子进程将不会继承文件的描述符使用半双工管道的时候,任何关联的进程都需要共享一个相关的祖先进程。因为管道存储在系统内核中,不继承祖先将无法寻址,有名管道就不会出现这个问题
代码
#include <stdio.h>#include <unistd.h>#include <sys/wait.h>int main(void){int pipe_fd[2];int pipe_fd1[2];pid_t pid;char buf_r[100]={0};char * p_wbuf = nullptr;int r_num;//在创建子进程之前创建管道if(pipe(pipe_fd) < 0){printf("pipe create error!\n");_exit(-1);}if(pipe(pipe_fd1) < 0){printf("pipe create error!\n");_exit(-1);}printf("pipe create success!\n");// printf("pipe_fd[0] = %d,pipe_fd[1] = %d",pipe_fd[0],pipe_fd[1]);// printf("pipe_fd1[0] = %d,pipe_fd1[1] = %d",pipe_fd1[0],pipe_fd1[1]);pid = fork();//创建子进程//子进程执行的代码if (pid == 0){printf("******子进程******\n");//read pipeclose(pipe_fd[1]);r_num = read(pipe_fd[0],buf_r,100);if (r_num > 0){printf("children r_num = %d, pipe is: %s\n",r_num,buf_r);}close(pipe_fd[0]);//write pipeclose(pipe_fd1[0]);if (write(pipe_fd1[1],"ByeBye!",7)!=-1){printf("children process write ByeBye! success!\n");}close(pipe_fd1[1]);printf("children process exit!\n");_exit(0);} else if (pid > 0){printf("******父进程******\n");//write pipeclose(pipe_fd[0]);if (write(pipe_fd[1],"Hello!",6) !=-1){printf("father process write Hello! success!\n");}close(pipe_fd[1]);//read processclose(pipe_fd1[1]);r_num = read(pipe_fd1[0],buf_r,100);if (r_num > 0){printf("father r_num = %d, pipe is: %s\n",r_num,buf_r);}close(pipe_fd1[0]);wait(nullptr);printf("father process exit!\n");_exit(0);}return 0;}
父进程 给 子进程 传输 Hello!;接收输出子进程的 ByeBye!子进程 给 父进程 传输ByeBye!;接收输出父进程的 Hello!子进程退出,然后父进程退出r_num 为读取的数据长度