1什么是进程:进程是一个执行中的程序
执行的程序: 代码->资源->CPU
进程有很多数据维护:进程状态/进程属性 所有进程属性采用的一个树形结构体维护 ps -a//所有进程 ps -aue //有效进程 进程状态:(man ps) D Uninterruptible sleep (usually IO) R Running or runnable (on run queue) S Interruptible sleep (waiting for an event to complete) T Stopped, either by a job control signal or because it is being traced. W paging (not valid since the 2.6.xx kernel) X dead (should never be seen) Z Defunct ("zombie") process, terminated but not reaped by its parent. For BSD formats and when the stat keyword is used, additional characters may be displayed: < high-priority (not nice to other users) N low-priority (nice to other users) L has pages locked into memory (for real-time and custom IO) s is a session leader l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do) 多进程 + is in the foreground process group 前台进程 top 实时查看进程 pstree 查看进程树 kill 向进程发送信号 kill -s 信号 进程ID kill -l //查看所有信号 kill -s 9 224 //关闭224进程2 创建进程
1 进程有关的创建函数
1.1 system
int system(const char*filename);
建立一个独立的进程,拥有独立的代码空间 等待新的进程执行完毕,system才返回(阻塞) 使用system调用函数(观察进程ID 观察阻塞) 新进程的返回值和system返回值有关系 1任何进程的返回值不要超过255 2 system中进程的返回值存放在system返回值的8-15位 可以通过printf("%d\n",r>>8 ); 这样来获得返回值 3 linux中有专用的获得返回状态的宏 WEXITSTATUS(status) //#include<wait.h> #include <sys/wait.h> sys.c#includegcc sys.c -o sleep gcc sys2.c ./a.out#include int main() { printf("%d\n",getpid()); sleep(2); return 254; } sys2.c #include #include #include #include int main() { int r; printf("%d\n",getpid()); r = system("./sleep"); //printf("%d\n",r>>8 ); printf("%d\n",WEXITSTATUS(r)); }
1.2 popen:创建子进程
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
案例:使用popen调用ls -l 并且建立一个管道读取输出
#include#include int main(){ FILE *fp = popen("cat file","r"); //注意这里是读取输出,不是打开文件 不能直接写文件名 //FILE *fp = popen("ls -l","r"); if(!fp) { perror("popen"); return 1; } int fd = fileno(fp); int r; char buf[1024] = {0}; while((r=read(fd,buf,1023)) > 0) { buf[r] = 0; printf("%s",buf); } pclose(fp); }
1.3 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, ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); 作用: 替换当前代码空间中的数据 函数本身不创建新的进程 第一个参数:替换的程序 第二个参数: 命令行 命令行格式:命令名 选项参数 NULL 命令行结尾必须是空字符串 execl execlp的区别: execl 只在当前路径搜索 execlp 可以使用系统搜索路径(which能找到) 如果都找不到,可以使用绝对路径 命令锁指向的程序 命令本身 参数列表 如果返回-1 失败#includeint main(){ int r = execl("/bin/ls","ls","-l",0); // 只能调用当前路径// int r = execlp("ls","ls","-l",0);// 能调用系统路径 printf("调用结束%d\n",r); return 0;}
1.4 fork
pid_t fork()
1 创建进程
2 新进程的代码是什么:克隆父进程的挨骂 而且克隆来执行的位置 3 在子进程中不调用fork 所有返回值=0 4 父子进程同时执行#include#include int main(){ printf("创建进程前\n"); int pid = fork(); //父进程执行的 while(1){ if(pid == 0) { printf("子进程 %d\n",getpid()); } else { printf("父进程 %d\n",getpid()); } } }
3 应用进程
使用fork实现多任务(unix本身是不支持线程的)
1 进程 2 线程 3 信号 4 异步 5 进程池与线程池4 理解进程
1父子进程的关系
独立的两个进程 互为父子关系 使用pstree看到 ├─gnome-terminal─┬─bash───a.out───a.out //父子关系 │ ├─bash───pstree │ ├─gnome-pty-helpe │ └─2*[{gnome-terminal}] 2 问题: 1如果父进程先结束 子进程在么办 子进程将变成孤儿进程,直接依托根进程(init) 孤儿 进程是没有危害的 init─┬─NetworkManager─┬─dhclient │ └─{NetworkManager} ├─a.out 2 如果子进程先结束 父进程怎么办 子进程先结束,子进程会成为僵尸进程。 僵尸进程的特点: 不占用内存 cpu,但在进程任务管理树上占用一个节点(宝贵) 实际上僵尸进程会造成进程名额的资源浪费。一定要处理僵尸进程 ├─gnome-terminal─┬─bash───pstree │ ├─bash───a.out───a.out 3 僵尸进程使用wait回收(阻塞函数) #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); wait 阻塞直到任意进程结束,status来接收进程返回值,返回值表示返回的进程号 waitpid 阻塞直到指定进程结束 WEXITSTATUS(status) 解析返回值 status的 8-15位是进程的返回值 4 父进程怎么知道子进程退出? 子进程结束时,通常会向父进程发送一个SIGCHLD信号 5父进程处理子进程的信号 #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); signal 的功能 : 向系统注册,只要接收到信号signal,系统停止进程,执行handler函数, 当函数执行完毕,继续原来的进程 (软中断) 5.1实现处理函数 5.2 使用signal绑定信号与函数 只有当子进程退出时才用wait,因为wait是一个阻塞函数。所以wait和signal一起用。#includezhao@ubuntu:~/unix/5$ ./a.out 创建进程前 父进程 2324 子进程 2325 parent parent parent parent 回收中 回收完毕88 parent parent parent ^C#include #include #include #include void deal(int s){ printf("回收中\n"); sleep(5); int status; wait(&status); printf("回收完毕%d\n",WEXITSTATUS(status));}int main(){ printf("创建进程前\n"); int pid = fork(); //父进程执行的 if(pid == 0) { printf("子进程 %d\n",getpid()); sleep(5); return 88; } else { printf("父进程 %d\n",getpid()); signal(SIGCHLD,deal); while(1) { sleep(1); printf("parent\n"); } return 0; } }
6 父子进程的内存空间
6.1全局变量 局部变量 堆变量 都会被子进程拷贝,但与原先的独立。
注意 堆内存被复制了,需要分别在各个进程中手动释放。
子进程克隆了父进程的全局区和局部区内存,但内存区域指向不同的物理空间。 尽管克隆但内存独立,不能相互访问。 进程间通信(IPC)是大问题。6.2 内存映射与子进程:
内存映射的属性,决定子进程和父进程是否映射在同一物理空间。
MAP_SHARED: 映射到同一物理空间。 (改一个进程中的,其他进程的也变化) MAP_PRIVATE:映射到不同的物理空间。#include#include #include int main(){ int *a = mmap(0,4,PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED,0,0); *a = 20; int pid = fork(); //父进程执行的 if(pid == 0) { *a= 90; printf("parent :a=%d\n",*a);//90 } else { sleep(3); printf("child :a=%d\n",*a);//90 } }
因为使用的是MAP_SHARED ,所以射到了同一物理空间, 改动会影响其它的.
若使用MAP_PRIVATE 就可以映射到不同物理空间.
6.3 文件描述符的拷贝
每个进程都维护一个文件描述符列表。
父子进程间,拷贝了文件描述符, 相同文件描述符指向的是同一个文件内核对象。 1 各个进程的文件描述符都需要close。 2 对文件的读写会改变文件对象的读写指针位置,对所有进程都有影响。#include进程的数据交换,基于两种方式: 内存: 有序/无序:mmap 文件:有序/无序:普通文件 基于内核对象:文件/内存/队列#include #include #include int main(){ int fd = open("test.txt",O_RDWR); int pid = fork(); //父进程执行的 if(pid == 0) { printf("parent:\n"); char buf[1024] ={0}; lseek(fd,0,SEEK_SET); read(fd,buf,1023); printf("%s\n",buf); close(fd); } else { printf("child:\n"); char buf[1024] ={0}; lseek(fd,0,SEEK_SET); read(fd,buf,1023); printf("%s\n",buf); close(fd); } }