七天的国庆假期已经结束,紧接着的是七天的工作日,前几天的我感叹假期七天有多短,此时此刻的我就感叹七天有多长!
言归正传,本周的前五天课里,紧接着假期前的IO进程课程,第一天学习了:进程中各种常用函数的接口,多进程中的守护进程;第二天学习了:多线程的创建、执行、退出和回收和线程中各种常用函数的接口;第三天学习了:线程间的通信,包括互斥锁、无名信号量和条件变量,传统进程间通信方式的一部分,包括无名管道和有名管道;第四天学习了:传统进程间通信中的信号,SystemV引入的IPC进程间通信中的消息队列和共享内存;第五天学习了:SystemV引入的IPC进程间通信中的信号灯集,下午进行了这门课的考试,最后会附上这套试卷的改错和分析。
这门课的主要特点是函数接口多、要熟悉和记忆的内容多、易混淆的知识点多,这几天的内容中,重点和难点是线程间通信和进程间通信,因此,本次的总结反思将针对这几方面,进行整理归纳,难点分析和易混淆点辨析。
同样,写此文章,是想以这种碎碎念的方式回顾重点、重复盲点、加深印象,复习、总结和反思本周的学习,仅供后期自己回顾使用。知识体系不够完善,内容也不够详细,仅供笔者复习使用。如果是有需要源码和笔记、或者对这方面感兴趣的同学,都可以私信我,发你完整的知识体系和详细内容的笔记。如有任何错误请多指正,也欢迎友好交流,小弟定会虚心听取大佬们的宝贵意见!
直接从字面意思理解,获取当前进程的pid和父进程的pid,功能参数返回值如下,着重注意这两个函数的返回值。
pid_t getpid(void);
功能:获取当前进程的进程号
参数:
@无
返回值:返回当前进程的pid
pid_t getppid(void);
功能:获取父进程的进程号
参数:
@无
返回值:返回父进程的pid
字面意思理解,退出(结束)进程的函数;
注意两个函数的区别:exit结束进程时会刷新缓存区,_exit结束进程时,不会刷新缓存区;
和 return 区分:return本身并不具备结束进程的功能,只有当return在main函数中使用的时候
才具备结束进程的功能。
#include
void exit(int status);
功能:用来结束一个正在执行的进程,会刷新缓冲区(库函数)
参数:
@status:进程退出状态值
#define EXIT_FAILURE 1 //失败退出
#define EXIT_SUCCESS 0 //成功退出
返回值:无
#include
void _exit(int status);
功能:用来结束一个正在执行的进程,不会刷新缓冲区(系统调用)
参数:
@status:进程退出状态值
返回值:无
练习:
程序代码如下,请按执行顺序写出输出结果。
#include
int main()
{
pid_t pid1, pid2;if ((pid1 = fork()) == 0){
sleep(3);
printf("info1 from child process_1\n");
exit(0);
printf("info2 from child process_1\n");} else {
if ((pid2 = fork()) == 0) {
sleep(1);
printf("info1 from child process_2\n");
exit(0);} else {
wait(NULL);
wait(NULL);
printf("info1 from parent process\n");
printf("info2 from parent process");
_exit(0);
}
}
}分析:
exit结束进程时会刷新缓存区,_exit结束进程时,不会刷新缓存区。
答案:
info1 from child process_2
info1 from child process_1
info1 from parent process
如果子进程使用exit/_exit退出,此时子进程的资源没有回收,子进程的状态会变成僵尸态,wait/waitpid函数的作业是为僵尸进程手动回收资源。
注意:回收资源时,一次只能回收掉一个进程的资源,wait不能指定进程,谁先结束回收谁,waitpid可以指定回收。
wait(NULL) = waitpid(-1,NULL,0)
#include
#include
pid_t wait(int *wstatus);
功能:阻塞回收子进程的资源(没有指定回收那个子进程的资源,谁先结束回收谁)
(一次只能回收掉一个进程的资源)
参数:
@wstatus:子进程退出的状态
返回值:成功返回回收掉的子进程的pid,失败返回-1置为错误码
#include
#include
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:可以指定回收某个进程的资源
参数:
@pid:进程号
< -1 先对这个值取绝对值,然后同组的任意的子进程的资源
-1 回收任意子进程的资源
0 回收同组的任意子进程资源
> 0 回收pid指定的进程的资源
@wstatus:进程退出的状态
@options:回收资源的方式
0:阻塞方式回收资源
WNOHANG:非阻塞方式回收资源
返回值:成功返回pid,如果时非阻塞方式没有回收掉子进程的资源返回0
失败返回-1,置位错误码
1.1 什么是守护进程
守护进程就是后台运行的服务,随系统的启动而启动,随系统的关闭而关闭。
守护进程可以脱离终端运行。
1.2 创建守护进程的流程
1. 创建脱离终端的进程(孤儿进程);
2. 修改孤儿进程的sid和gid(调用setsid函数);
3. 切换当前进程的目录(调用chdir函数);
4. 设置umask值(调用umask函数);
5. 文件描述符重定向(调用dup、dup2函数);
1.3 使用到的函数接口
修改孤儿进程的sid和gid
pid_t setsid(void); 功能:将当前进程的pid,设置位组id和会话id 参数: @无 返回值:成功返回pid,失败返回-1置为错误码
切换当前进程的目录
int chdir(const char *path); 功能:切换目录 参数: @path:路径 "/" 返回值:成功返回0,失败返回-1置位错误码
设置umask值
mode_t umask(mode_t mask); 功能:设置文件掩码值 参数: @mask:掩码 0 返回值:总是会成功,返回mask
进行文件描述符的重定向
int dup(int oldfd); 功能:拷贝oldfd生成新的newfd,这个newfd采用最小未分配的原则分配, 新旧文件描述符都可以操作同一个文件,他们公用光标和文件的状态。 参数: @oldfd:旧的文件描述符 返回值:成功返回新的文件描述符,失败返回-1置位错误码 int dup2(int oldfd, int newfd); 功能:将oldfd重定向位newfd,如果newfd对应的有打开的文件,就会把文件先关闭在重用 以后对newfd操作就相当于对oldfd操作,共用光标和文件状态 参数: @oldfd:旧的文件描述符 @newfd:新的文件描述符 返回值:成功返回新的文件描述符,失败返回-1置位错误码
这一步可以直接用下面这三行总结,即将标准输入、标准输出和标准出错都重定向到fd。
dup2(fd,0); dup2(fd,1); dup2(fd,2);
练习:
创建一个脱离终端、后台运行的守护进程
#include
int main(int argc,const char * argv[]) { pid_t pid; int fd; pid = fork(); if(pid ==-1){ PRINT_ERR("fork error"); }else if(pid==0){ //1.孤儿进程 //2.设置会话id和组id if(setsid()==-1) PRINT_ERR("setsid error"); //3.切换目录 if(chdir("/")) PRINT_ERR("chdir error"); //4.设置umask umask(0); //5.创建日志文件 if((fd = open("mylog.log",O_RDWR|O_CREAT|O_APPEND,0666))==-1) PRINT_ERR("open error"); //6.进行文件描述符重定向 dup2(fd,0); dup2(fd,1); dup2(fd,2); //7.开启自己的服务 while(1){ printf("i am test daemon process...\n"); fflush(stdout); sleep(1); } }else{ exit(EXIT_SUCCESS); } return 0; }
一、进程中用来对可执行程序进行替换的函数
1.1 system函数
system函数的作用,简单理解就是实现在C语言代码中,执行linux命令,功能参数返回值如下:
#include
int system(const char *command);
功能:在一个进程中fork出来一个子进程,在子进程内执行command这个命令(或者可执行程序),在system函数执行完才会返回
参数:
@command:可执行程序路径及名字
返回值:
如果是非0代表函数执行失败,如果是0代表成功
*如果command为NULL,则如果shell可用,则为非零值;
如果没有shell可用,则为0。
*如果无法创建子进程,或者无法检索其状态,则返回值为-1。
*如果一个shell不能在子进程中执行,那么返回值_exit(127)
*如果所有系统调用都成功,则返回值是用于执行命令的子shell的终止状态。(shell的终止状态是它执行的
最后一个命令的终止状态。)
1.2 exec函数族
exec函数族中包含:execl、execv、execlp、execvp、execle、execvpe;
功能简单理解就是,用另外的程序替换掉当前进程中,正在执行的程序。
注意:函数执行后,后面的被替换掉的函数就不会被执行了。
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
功能:替换当前进程正在执行的程序
参数:
@path:可执行程序的路径及名字 "/bin/ls"
@arg:执行可执行程序的参数列表 "ls","-lh",NULL
返回值:只有执行失败会返回,返回-1置位错误码
int execv(const char *path, char *const argv[]);
功能:替换当前进程正在执行的程序
参数:
@path:可执行程序的路径及名字 "/bin/ls"
@arg:执行参数的容器 argv[] = "ls","-lh",NULL
返回值:只有执行失败会返回,返回-1置位错误码
execlp/execvp在执行程序替换的时候,如果可执行程序的路径通过PATH环境变量指定了在填写第一个参数的时候可以不用写路径,而直接写可执行程序的名字。
( PATH变量的位置,sudo vi /etc/environment )
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
功能:替换当前进程正在执行的程序
参数:
@file:可执行程序的名字 "ls"
@arg:执行可执行程序的参数列表 "ls","-lh",NULL
返回值:只有错误会返回,返回-1置位错误码
int execvp(const char *file, char *const argv[]);
功能:替换当前进程正在执行的程序
参数:
@file:可执行程序的名字 "ls"
@arg:执行参数的容器 argv[] = "ls","-lh",NULL
返回值:只有错误会返回,返回-1置位错误码
execle和execvpe的特点是,可以指定目录下的可执行来替换当前的进程中的程序。
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
功能:替换当前进程正在执行的程序
参数:
@path:可执行程序的路径及名字 "/bin/ls"
@arg:执行可执行程序的参数列表 "ls","-lh",NULL
@envp:想传递的环境变量
返回值:只有错误会返回,返回-1置位错误码
int execvpe(const char *file, char *const argv[], char *const envp[]);
功能:替换当前进程正在执行的程序
参数:
@file:可执行程序的名字 "ls"
@arg:执行可执行程序的参数列表 "ls","-lh",NULL
@envp:想传递的环境变量
返回值:只有错误会返回,返回-1置位错误码
线程(Light Weight Process,LWP),即轻量级的进程,进程是分配资源的最小单位,线程是调度的最小单位。
线程大小为8k,共享进程的内存(0-3G),现成的大小基本可以忽略不计,通常认为线程不占内存。
多线程没有多进程安全,因为多线程共用同一个进程的资源,如果多线程中的一个线程导致进程崩溃,所有的其他的线程也会终止;多进程中,进程和进程资源独立,互不影响。
多线程的效率要比多进程高。
线程的函数是通过第三方库实现的libpthread.so,所以在使用线程创建函数的时候需要-lpthread链接第三方库。线程的函数的man手册需要通过sudo apt-get install安装sudo apt-get install manpages-posix manpages-posix-dev
获取线程信息的命令: ps -eLf
线程执行和进程一样,没有先后顺序,时间片轮序,上下文切换。
多线程在内存上是共用的,一个线程将变量改掉之后,另外一个线程能拿到修改后的值。
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建一个线程
参数:
@thread:获取到线程号
@attr:线程的属性 一般NULL
@start_routine:线程体
@arg:向线程体传递参数
返回值:成功返回0,失败返回错误码
pthread_t pthread_self(void);
功能:获取当前线程的线程号
参数:
@无
返回值:总是会成功,成功返回线程号
void pthread_exit(void *retval);
功能:退出当前的线程
参数:
@retval:线程退出的状态值
返回值:无
int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待回收线程的资源(线程必须是结合态)
参数:
@thread:线程号
@retval:接收pthread_exit退出时候的状态值
返回值:成功返回0,失败返回错误码
线程有两种状态:结合态和分离态
结合态的线程结束后需要调用pthread_join来回收资源,如果没有调用pthread_join当进程结束的时候
资源也是可以被回收调用。
分离态的线程在结束后资源会被自动回收
int pthread_detach(pthread_t thread);
功能:将线程标记为分离态
参数:
@thread:线程号
返回值:成功返回0,失败返回错误码
int pthread_cancel(pthread_t thread);
功能:取消thread线程的执行
参数:
@thread:线程号
返回值:成功返回0,失败返回非0