本文共 6179 字,大约阅读时间需要 20 分钟。
atexit函数多次注册的时候先注册的后执行,就像压栈。
用_exit终止进程是并不执行atexit注册的进程终止处理函数。
2、进程控制块PCB(process control block),就是内核中用来管理进程的数据结构。
API函数:getpid、getppid、getuid、geteuid、getgid、getegid
fork函数调用一次会返回两次,返回值等于0的就是我们就是的子进程,而返回大于0的就是父进程。
printf("\n子进程pid = %d\n",getpid());//一定子进程
printf("在子进程里面,父进程的ID = %d\n",getppid());
printf("\n父进程pid = %d\n",getpid());//一定是父进程
printf("在父进程里面 p1 = %d\n",p1);
//printf("Fenton 子进程和父进程都运行了这段程序pid = %d\n",getpid());
这里会出现在子进程中用getppid函数会出问题,因为在子进程运行的时候父进程已经死掉了,所有获取的有问题。
父子进程对文件的操作:在子进程和父进程里面对同一文件操作,对应fd的文件指针是关联的向O_APPEND之后的的样子。有时候看到的只有一个,有点想分别写,原因是程序台简短了;父进程和子进程独自打开一个文件,O_APPEND可以把父子进程独自打开的fd文件指针关联起来实现分别写。测试代码如下:
fd1 = open("1.txt",O_RDWR | O_APPEND);
printf("在子进程里面,父进程的ID = %d\n",getppid());
fd1 = open("1.txt",O_RDWR|O_APPEND);
printf("在父进程里面 pid = %d\n",pid);
父进程在没有fork之前自己多事情对子进程没有影响,但是父进程fork之后在自己的if做的事情对子进程就没有影响了,本质原因就是在fork内部复制了父进程的PCB生成了一个新的子进程,并且fork返回是子进程已经完全和父进程脱离并且独立被OS调度执行。
进程由fork开始,终究进程也会消亡。linux中malloc和open之后没有free和close,在进程结束之后会自动回收。但是回收的时候没有回收干净,只是回收工作的时候的内存和IO,但是没有会后进程本身的内存(8kb,也就是task_struct和栈内存),所以每一个进程都需要一个帮他回收的东西,这个东西就是父进程。
1、子进程先于父进程结束,子进程结束,父进程并不一定理解帮子进程“收尸”,在这段之间,就成为僵尸进程。
2、子进程除task_struct和栈外其余内存空间已清理
3、父进程可以使用wait或waitpid以显式回收子进程的剩余待回收内存资源并且获取子进程退出状态。
4、父进程也可以不适用wait或者waitpid回收子进程,此时父进程结束时一样回收进程(为了防止父进程忘记显式调用wait来收回内存而造成的内存泄漏)
2、linux系统规定,所有孤儿进程都自动成为一个特殊进程(进程1,也就是init进程)的子进程。
WIFEXITED用来判断子程序是否正常退出(return exit _exit)
WIFSIGNALED用来判断子进程是否非正常终止(被信号所终止)
WEXITSTATUS用来得到子程序正常终止的返回值(返回无符号数)
waitpid就是等待回收指定pid的进程,可以工作在阻塞和非阻塞两种模式。
ret = waitpid(-1, &status, 0);
-1表示不等待某个特定的PID,0表示默认的方式(阻塞)。ret表示返回值
ret = waitpid(pid, &status, 0);阻塞等待回收后PID为pid的这个子进程。
ret = waitpid(pid, &status, 1);非阻塞等待回收后PID为pid的这个子进程。只要一调用,就马上返回。只在返回值里面标志。
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 *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
execl和execv函数值最基本的exec族函数,区别是传参的格式不同,execl是把参数列表(必须以NULL结束)一次排列而成,l就是list的缩写。execv是事先把参数列表放在一个字符串数组里面,然后再传参。
execlp和execvp这两个在上面基础上加了p,较上面上个说,上面两个执行需要指定可执行的全路径(execl没有找到path这个文件则会报错),而加了p可以是file(也可以也是path,只是兼容了file,加了p的首先去找file,找到则执行,如果没找到则回去环境变量PATH所指的目录下去找,找到执行,找不到就报错)。
execle和execvpe,这两个函数比较上面,函数原型中的参数列表也多了一个字符串数组envp形参,e就是envirment,区别就是执行可执行程序,会多传一个环境变量给执行程序。
测试代码如下:#include <stdio.h>
//char *const arg[] = {"ls","-a","-l",NULL};
//execv("/bin/ls",arg);//执行ls命令
execl("./hello.o","aaa",NULL);
printf("在父进程里面,子进程的pid = %d\n",pid);
execlp和execvp,不加了p的需要全路径+文件名字,找不到就报错,加了p就到PATH指定的路径下面去找文件。优先环境变量PATH中去找,找不到再来当前文件下面找。
execle和execvpe:main函数的原型不止是int main(int argc, char **argv),还可以是int main(int argc, char **argv, char **env),env就是就是给main函数传递的环境变量,第三个是字符串数组,内容是字符串数组,若是没有传递第三个参数,则会从父进程继承了一份环境变量(OS默认)。
进程的状态:就绪态、运行态、僵尸态、等待态、停止态。
原子操作:整个操作一旦来时就会不被打断的执行完,原子操作的好处就是不会被打断(不会引来竞争状态);坏处是自己单独连续占中CPU时间太长,影响实时性,应该尽量避免不必要的原子操作,就算不得不使用原子操作,也应该尽量减少原子操作的时间。
kill命令 用法 kill -信号编码 进程ID
1、daemon就是守护进程的意思,简称d(进程名字后面带d的基本就是守护进程)
syslogd,系统日志守护进程,提供syslog功能
cron,用来实现时间管理的,在linux定时执行程序的功能就要用到。
chdir("/");//设置当前进程工作目录为根目录
umask(0);//设置为0,确保进程对文件有最大的操作权限
//关闭所有文件描述符,这里需要动态获取,获取当前系统允许对打的文件描述虎
for(int i = 0; i < sysconf(_SC_OPEN_MAX); i++)
open("/dev/null",O_RDWR);
open("/dev/null",O_RDWR);
open("/dev/null",O_RDWR);
openlog("a.out", LOG_PID | LOG_CONS, LOG_USER);
syslog(LOG_INFO, "this is my loginfo\n");
在守护进程中有一个syslogd,这个进程就是负责日志文件的写入和维护。
怎么能让程序不能被多次运行,在守护进程中,是长时间运行的,不退出。因此./a.out
多次运行就是有多个进程,这并不是我们想要的。有时候我们希望程序是单利运行,意思就是当我们运行的程序没有运行过,就运行,若是之前已经有一个程序的进程在运行,本次运行直接退出(提示程序在运行)。最常用的做法就是在执行的开始之初判断特定的文件是否存在,若存在则标明进程已经在运行了。
这个文件特定的文件要古怪一点,确保不会凑巧真的在电脑中存在的。
同一个进程在一个地址空间之中,同一个进程的不同模块之间很多时候都是通过全局变量,也可以通过函数形参传递;
不同进程处于不同的地址空间,因此要互相通信很难。99%不需要考虑进程间通行,因为大部分程序都是单进程的(可以通过多线程的方式解决并发问题),复杂大型的程序,因此设计的需要就被设计成多进程通信,常见的GUI程序,服务器等
结论:IPC技术在一般的中小型程序要办用不到,,在大型程序中才会用到。
管道:管道是单向通信的,是半双工的。只能在父子间通行
转载地址:http://ugtnmu.baihongyu.com/