mjpg-streamer学习笔记3----主进程
2016-09-01 18:23
543 查看
1、主进程框架图
![](http://img.blog.csdn.net/20160901205602195?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
2、主函数分析
int main(int argc, char *argv[])
{
input指针未能赋值时使用默认值input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0
char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";//指向输入选项的参数字符串
char *output[MAX_OUTPUT_PLUGINS];/* 指向输出选项的参数字符串 */
int daemon=0, i;/* 是否让程序在后台运行的标志 */
size_t tmp=0;
output[0]初始值,设置默认端口号为8080
output[0] = "output_http.so --port 8080";
global.outcnt = 0;/* 此时输出通道有几种方式, 0 */
mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"
/* parameter parsing */ 运行mjpg_streamer命令时所带入的参数,while循环用于解析这些参数
/* 解析参数(关键是看懂 getopt_long_only 函数) */
/*
getopt_long_only():
用于解析命令行选项
根据传入的参数,一个一个的在struct option 数组里面进行匹配,当匹配到相同的参数,返回在数组中的下标。如传入-i选项 ,argv中自然有i选项,就会和struct option 数组项进行匹配,找到时返回数组下标。option_index索引值为2.
参数解析:
第二个参数:直接从main函数传递而来,表示我们传入的参数
-i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"
第三个参数:短选项字符串
第四个参数:是 struct option 数组,用于存放长选项参数
第五个参数:用于返回长选项在longopts结构体数组中的索引值
返回值:
解析完毕,返回-1
出现未定义的长选项或者短选项,getopt_long返回?
注意:
长选项必须加'-'
*/
while(1)
{
int option_index = 0, c=0;
static struct option long_options[] = \
{
{"h", no_argument, 0, 0},
{"help", no_argument, 0, 0},
{"i", required_argument, 0, 0},
{"input", required_argument, 0, 0},
{"o", required_argument, 0, 0},
{"output", required_argument, 0, 0},
{"v", no_argument, 0, 0},
{"version", no_argument, 0, 0},
{"b", no_argument, 0, 0},
{"background", no_argument, 0, 0},
{0, 0, 0, 0}
};
/* argv = " -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www" " */
判断getopt_long_only返回值,当为-1时表示参数解析完成,跳出循环。
c = getopt_long_only(argc, argv, "", long_options, &option_index);
/* no more options to parse */
/* 参数解析完成 */
if (c == -1)
break;
如果传人的参数不正确,则打印帮助信息
/* unrecognized option */
/* 如果传人的参数不正确,则打印帮助信息 */
if(c=='?')
{
help(argv[0]);
return 0;
}
根据返回的结构体数组的下标,来执行相应的选项
switch (option_index)
{
/* h, help */
case 0:
case 1:
help(argv[0]);
return 0;
break;
/* i, input */
case 2:
case 3:
在getopt_long_only函数中,把传入的-i选项对应的字符串"input_uvc.so -f 10 -r 320*240"赋给变量optarg,而strdup()函数用于将变量optarg的值赋予指针input
input = strdup(optarg);// input = "input_uvc.so -f 10 -r 320*240"
break;
/* o, output */
case 4:
case 5:
output[global.outcnt++] = strdup(optarg);// output[0] = "output_http.so -w www"
break;
/* v, version */
case 6:
case 7:
printf("MJPG Streamer Version: %s\n" \
"Compilation Date.....: %s\n" \
"Compilation Time.....: %s\n", SOURCE_VERSION, __DATE__, __TIME__);
return 0;
break;
参数传入-b选项时,让daemon=1;让程序在后台运行
/* b, background */
case 8:
case 9:
daemon=1;
break;
default:
help(argv[0]);
return 0;
}
}
/* 打开一个程序的系统记录器的链接 */也就是打开一个调试用的记录本相当于log.txt
openlog("MJPG-streamer ", LOG_PID|LOG_CONS, LOG_USER);
//openlog("MJPG-streamer ", LOG_PID|LOG_CONS|LOG_PERROR, LOG_USER);
syslog(LOG_INFO, "starting application");// 将 "starting application" 字符串写到系统记录中
/* fork to the background */
/* 如果daemon = 1,则让程序在后台运行 */
if ( daemon )
{
LOG("enabling daemon mode");
daemon_mode();
}
初始化 global 中的成员
/* initialise the global variables */
/* 初始化 global 中的成员 */
global.stop = 0;
global.buf = NULL;
global.size = 0;
global.in.plugin = NULL;
/* this mutex and the conditional variable are used to synchronize access to the global picture buffer */
if( pthread_mutex_init(&global.db, NULL) != 0 )/* 初始化 global.db 成员 ,是互斥锁相关的*/
{
LOG("could not initialize mutex variable\n");
closelog();
exit(EXIT_FAILURE);
}
if( pthread_cond_init(&global.db_update, NULL) != 0 )/* 初始化 global.db_update(条件变量) 成员,进程里线程间进行通信的变量 */
{
LOG("could not initialize condition variable\n");
closelog();
exit(EXIT_FAILURE);
}
/* ignore SIGPIPE (send by OS if transmitting to closed TCP sockets) */
signal(SIGPIPE, SIG_IGN);/* 忽略 SIGPIPE 信号 */
/* register signal handler for <CTRL>+C in order to clean up */
/*
signal()函数是信号绑定,当我们按下 <CTRL>+C 时,则调用signal_handler()函数,做一些清理工作
*/
if (signal(SIGINT, signal_handler) == SIG_ERR)
{
LOG("could not register signal handler\n");
closelog();
exit(EXIT_FAILURE);
}
/*
* messages like the following will only be visible on your terminal
* if not running in daemon mode
*/
LOG("MJPG Streamer Version.: %s\n", SOURCE_VERSION);// 打印出 MJPG Streamer 版本号
/* check if at least one output plugin was selected */
if ( global.outcnt == 0 )// 如果输出方式的种类为0,则让他为1
{
/* no? Then use the default plugin instead */
global.outcnt = 1;
}
/* open input plugin */
/* "input_uvc.so -f 10 -r 320*240" */
strchr函数原型:extern char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置,返回值:成功则返回要查找字符第一次出现的位置,失败返回NULL
tmp = (size_t)(strchr(input, ' ')-input);// 让tmp 等于 "input_uvc.so"字符串的长度
如果tmp大于0,就取字符串input的前tmp位,也就是input_uvc.so
global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);// global.in.plugin = "input_uvc.so"
global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);// 打开 "input_uvc.so" 这个动态链接库
判断返回值
if ( !global.in.handle )
{
LOG("ERROR: could not find input plugin\n");
LOG(" Perhaps you want to adjust the search path with:\n");
LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
LOG(" dlopen: %s\n", dlerror() );
closelog();
exit(EXIT_FAILURE);
}
global.in.init = dlsym(global.in.handle, "input_init");// 让 global.in.init = input_init
global.in.init等于 刚打开的动态链接库(input_uvc.c)的input_init函数
if ( global.in.init == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.in.stop = dlsym(global.in.handle, "input_stop");// 让 global.in.stop = input_stop,global.in.stop等于 刚打开的动态链接库(input_uvc.c)的input_stop函数
if ( global.in.stop == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.in.run = dlsym(global.in.handle, "input_run");// 让 global.in.run = input_run,global.in.run等于 刚打开的动态链接库(input_uvc.c)的input_run函数
if ( global.in.run == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
/* try to find optional command */
global.in.cmd = dlsym(global.in.handle, "input_cmd");// global.in.cmd = input_cmd,global.in.cmd等于 刚打开的动态链接库(input_uvc.c)的input_cmd函数
global.in.param.parameter_string = strchr(input, ' ');// global.in.param.parameter_string = "-f 10 -r 320*240"
global.in.param.global = &global;
if ( global.in.init(&global.in.param) )// 调用input_uvc.c中的input_init(&global.in.param)函数
{
LOG("input_init() return value signals to exit");
closelog();
exit(0);
}
/* open output plugin */
for (i=0; i<global.outcnt; i++)// outcnt = 1
{
/* "output_http.so -w www" */
tmp = (size_t)(strchr(output[i], ' ')-output[i]);// 让tmp 等于 "output_http.so"字符串的长度
global.out[i].plugin = (tmp > 0)?strndup(output[i], tmp):strdup(output[i]);// 让 global.out[i].plugin = "output_http.so"
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);// 打开 "output_http.so" 动态链接库
判断返回值
if ( !global.out[i].handle )
{
LOG("ERROR: could not find output plugin %s\n", global.out[i].plugin);
LOG(" Perhaps you want to adjust the search path with:\n");
LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
LOG(" dlopen: %s\n", dlerror() );
closelog();
exit(EXIT_FAILURE);
}
global.out[i].init = dlsym(global.out[i].handle, "output_init");// 让 global.out[i].init = output_init,global.out[i].init等于 刚打开的动态链接库(Output_http.c)的output_init函数
if ( global.out[i].init == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].stop = dlsym(global.out[i].handle, "output_stop");// 让 global.out[i].stop = output_stop,global.out[i].stop等于 刚打开的动态链接库(Output_http.c)的output_stop函数
if ( global.out[i].stop == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].run = dlsym(global.out[i].handle, "output_run");// 让 global.out[i].run = output_run,global.out[i].run等于 刚打开的动态链接库(Output_http.c)的output_run函数
if ( global.out[i].run == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
/* try to find optional command */
global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");// 让 global.out[i].cmd = output_cmd,lobal.out[i].cmd等于 刚打开的动态链接库(Output_http.c)的output_cmd函数
参数字符串
global.out[i].param.parameter_string = strchr(output[i], ' ');// 让 global.out[i].param.parameter_string = "-w www"
global.out[i].param.global = &global;
global.out[i].param.id = i;
if ( global.out[i].init(&global.out[i].param) )// 调用 output_http.c中的output_init(&global.out[i].param)函数
{
LOG("output_init() return value signals to exit");
closelog();
exit(0);
}
}
/* start to read the input, push pictures into global buffer */
DBG("starting input plugin\n");// 打印调试信息 "starting input plugin\n"
syslog(LOG_INFO, "starting input plugin");//
将 "starting input plugin" 字符串写到记录本中
global.in.run();// 调用 input_uvc.c中的input_run函数
DBG("starting %d output plugin(s)\n", global.outcnt);// 打印调试信息
for(i=0; i<global.outcnt; i++)
{
syslog(LOG_INFO, "starting output plugin: %s (ID: %02d)", global.out[i].plugin, global.out[i].param.id);
global.out[i].run(global.out[i].param.id);// 调用 output_http.c 中的 output_run 函数
}
/* wait for signals */
pause();
// 程序等待信号的发生
return 0;
}
3、结论
主进程函数依次调用以下函数
![](http://img.blog.csdn.net/20160924112532634?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
2、主函数分析
int main(int argc, char *argv[])
{
input指针未能赋值时使用默认值input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0
char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";//指向输入选项的参数字符串
char *output[MAX_OUTPUT_PLUGINS];/* 指向输出选项的参数字符串 */
int daemon=0, i;/* 是否让程序在后台运行的标志 */
size_t tmp=0;
output[0]初始值,设置默认端口号为8080
output[0] = "output_http.so --port 8080";
global.outcnt = 0;/* 此时输出通道有几种方式, 0 */
mjpg_streamer -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"
/* parameter parsing */ 运行mjpg_streamer命令时所带入的参数,while循环用于解析这些参数
/* 解析参数(关键是看懂 getopt_long_only 函数) */
/*
getopt_long_only():
用于解析命令行选项
根据传入的参数,一个一个的在struct option 数组里面进行匹配,当匹配到相同的参数,返回在数组中的下标。如传入-i选项 ,argv中自然有i选项,就会和struct option 数组项进行匹配,找到时返回数组下标。option_index索引值为2.
参数解析:
第二个参数:直接从main函数传递而来,表示我们传入的参数
-i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www"
第三个参数:短选项字符串
第四个参数:是 struct option 数组,用于存放长选项参数
第五个参数:用于返回长选项在longopts结构体数组中的索引值
返回值:
解析完毕,返回-1
出现未定义的长选项或者短选项,getopt_long返回?
注意:
长选项必须加'-'
*/
while(1)
{
int option_index = 0, c=0;
static struct option long_options[] = \
{
{"h", no_argument, 0, 0},
{"help", no_argument, 0, 0},
{"i", required_argument, 0, 0},
{"input", required_argument, 0, 0},
{"o", required_argument, 0, 0},
{"output", required_argument, 0, 0},
{"v", no_argument, 0, 0},
{"version", no_argument, 0, 0},
{"b", no_argument, 0, 0},
{"background", no_argument, 0, 0},
{0, 0, 0, 0}
};
/* argv = " -i "input_uvc.so -f 10 -r 320*240" -o "output_http.so -w www" " */
判断getopt_long_only返回值,当为-1时表示参数解析完成,跳出循环。
c = getopt_long_only(argc, argv, "", long_options, &option_index);
/* no more options to parse */
/* 参数解析完成 */
if (c == -1)
break;
如果传人的参数不正确,则打印帮助信息
/* unrecognized option */
/* 如果传人的参数不正确,则打印帮助信息 */
if(c=='?')
{
help(argv[0]);
return 0;
}
根据返回的结构体数组的下标,来执行相应的选项
switch (option_index)
{
/* h, help */
case 0:
case 1:
help(argv[0]);
return 0;
break;
/* i, input */
case 2:
case 3:
在getopt_long_only函数中,把传入的-i选项对应的字符串"input_uvc.so -f 10 -r 320*240"赋给变量optarg,而strdup()函数用于将变量optarg的值赋予指针input
input = strdup(optarg);// input = "input_uvc.so -f 10 -r 320*240"
break;
/* o, output */
case 4:
case 5:
output[global.outcnt++] = strdup(optarg);// output[0] = "output_http.so -w www"
break;
/* v, version */
case 6:
case 7:
printf("MJPG Streamer Version: %s\n" \
"Compilation Date.....: %s\n" \
"Compilation Time.....: %s\n", SOURCE_VERSION, __DATE__, __TIME__);
return 0;
break;
参数传入-b选项时,让daemon=1;让程序在后台运行
/* b, background */
case 8:
case 9:
daemon=1;
break;
default:
help(argv[0]);
return 0;
}
}
/* 打开一个程序的系统记录器的链接 */也就是打开一个调试用的记录本相当于log.txt
openlog("MJPG-streamer ", LOG_PID|LOG_CONS, LOG_USER);
//openlog("MJPG-streamer ", LOG_PID|LOG_CONS|LOG_PERROR, LOG_USER);
syslog(LOG_INFO, "starting application");// 将 "starting application" 字符串写到系统记录中
/* fork to the background */
/* 如果daemon = 1,则让程序在后台运行 */
if ( daemon )
{
LOG("enabling daemon mode");
daemon_mode();
}
初始化 global 中的成员
/* initialise the global variables */
/* 初始化 global 中的成员 */
global.stop = 0;
global.buf = NULL;
global.size = 0;
global.in.plugin = NULL;
/* this mutex and the conditional variable are used to synchronize access to the global picture buffer */
if( pthread_mutex_init(&global.db, NULL) != 0 )/* 初始化 global.db 成员 ,是互斥锁相关的*/
{
LOG("could not initialize mutex variable\n");
closelog();
exit(EXIT_FAILURE);
}
if( pthread_cond_init(&global.db_update, NULL) != 0 )/* 初始化 global.db_update(条件变量) 成员,进程里线程间进行通信的变量 */
{
LOG("could not initialize condition variable\n");
closelog();
exit(EXIT_FAILURE);
}
/* ignore SIGPIPE (send by OS if transmitting to closed TCP sockets) */
signal(SIGPIPE, SIG_IGN);/* 忽略 SIGPIPE 信号 */
/* register signal handler for <CTRL>+C in order to clean up */
/*
signal()函数是信号绑定,当我们按下 <CTRL>+C 时,则调用signal_handler()函数,做一些清理工作
*/
if (signal(SIGINT, signal_handler) == SIG_ERR)
{
LOG("could not register signal handler\n");
closelog();
exit(EXIT_FAILURE);
}
/*
* messages like the following will only be visible on your terminal
* if not running in daemon mode
*/
LOG("MJPG Streamer Version.: %s\n", SOURCE_VERSION);// 打印出 MJPG Streamer 版本号
/* check if at least one output plugin was selected */
if ( global.outcnt == 0 )// 如果输出方式的种类为0,则让他为1
{
/* no? Then use the default plugin instead */
global.outcnt = 1;
}
/* open input plugin */
/* "input_uvc.so -f 10 -r 320*240" */
strchr函数原型:extern char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置,返回值:成功则返回要查找字符第一次出现的位置,失败返回NULL
tmp = (size_t)(strchr(input, ' ')-input);// 让tmp 等于 "input_uvc.so"字符串的长度
如果tmp大于0,就取字符串input的前tmp位,也就是input_uvc.so
global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);// global.in.plugin = "input_uvc.so"
global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);// 打开 "input_uvc.so" 这个动态链接库
判断返回值
if ( !global.in.handle )
{
LOG("ERROR: could not find input plugin\n");
LOG(" Perhaps you want to adjust the search path with:\n");
LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
LOG(" dlopen: %s\n", dlerror() );
closelog();
exit(EXIT_FAILURE);
}
global.in.init = dlsym(global.in.handle, "input_init");// 让 global.in.init = input_init
global.in.init等于 刚打开的动态链接库(input_uvc.c)的input_init函数
if ( global.in.init == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.in.stop = dlsym(global.in.handle, "input_stop");// 让 global.in.stop = input_stop,global.in.stop等于 刚打开的动态链接库(input_uvc.c)的input_stop函数
if ( global.in.stop == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.in.run = dlsym(global.in.handle, "input_run");// 让 global.in.run = input_run,global.in.run等于 刚打开的动态链接库(input_uvc.c)的input_run函数
if ( global.in.run == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
/* try to find optional command */
global.in.cmd = dlsym(global.in.handle, "input_cmd");// global.in.cmd = input_cmd,global.in.cmd等于 刚打开的动态链接库(input_uvc.c)的input_cmd函数
global.in.param.parameter_string = strchr(input, ' ');// global.in.param.parameter_string = "-f 10 -r 320*240"
global.in.param.global = &global;
if ( global.in.init(&global.in.param) )// 调用input_uvc.c中的input_init(&global.in.param)函数
{
LOG("input_init() return value signals to exit");
closelog();
exit(0);
}
/* open output plugin */
for (i=0; i<global.outcnt; i++)// outcnt = 1
{
/* "output_http.so -w www" */
tmp = (size_t)(strchr(output[i], ' ')-output[i]);// 让tmp 等于 "output_http.so"字符串的长度
global.out[i].plugin = (tmp > 0)?strndup(output[i], tmp):strdup(output[i]);// 让 global.out[i].plugin = "output_http.so"
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);// 打开 "output_http.so" 动态链接库
判断返回值
if ( !global.out[i].handle )
{
LOG("ERROR: could not find output plugin %s\n", global.out[i].plugin);
LOG(" Perhaps you want to adjust the search path with:\n");
LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
LOG(" dlopen: %s\n", dlerror() );
closelog();
exit(EXIT_FAILURE);
}
global.out[i].init = dlsym(global.out[i].handle, "output_init");// 让 global.out[i].init = output_init,global.out[i].init等于 刚打开的动态链接库(Output_http.c)的output_init函数
if ( global.out[i].init == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].stop = dlsym(global.out[i].handle, "output_stop");// 让 global.out[i].stop = output_stop,global.out[i].stop等于 刚打开的动态链接库(Output_http.c)的output_stop函数
if ( global.out[i].stop == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].run = dlsym(global.out[i].handle, "output_run");// 让 global.out[i].run = output_run,global.out[i].run等于 刚打开的动态链接库(Output_http.c)的output_run函数
if ( global.out[i].run == NULL )
{
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
/* try to find optional command */
global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");// 让 global.out[i].cmd = output_cmd,lobal.out[i].cmd等于 刚打开的动态链接库(Output_http.c)的output_cmd函数
参数字符串
global.out[i].param.parameter_string = strchr(output[i], ' ');// 让 global.out[i].param.parameter_string = "-w www"
global.out[i].param.global = &global;
global.out[i].param.id = i;
if ( global.out[i].init(&global.out[i].param) )// 调用 output_http.c中的output_init(&global.out[i].param)函数
{
LOG("output_init() return value signals to exit");
closelog();
exit(0);
}
}
/* start to read the input, push pictures into global buffer */
DBG("starting input plugin\n");// 打印调试信息 "starting input plugin\n"
syslog(LOG_INFO, "starting input plugin");//
将 "starting input plugin" 字符串写到记录本中
global.in.run();// 调用 input_uvc.c中的input_run函数
DBG("starting %d output plugin(s)\n", global.outcnt);// 打印调试信息
for(i=0; i<global.outcnt; i++)
{
syslog(LOG_INFO, "starting output plugin: %s (ID: %02d)", global.out[i].plugin, global.out[i].param.id);
global.out[i].run(global.out[i].param.id);// 调用 output_http.c 中的 output_run 函数
}
/* wait for signals */
pause();
// 程序等待信号的发生
return 0;
}
3、结论
主进程函数依次调用以下函数
相关文章推荐
- mjpg-streamer学习笔记4----主进程--关键函数详解
- mjpg-streamer学习笔记9---自己写客户端
- Mjpeg-streamer源码学习笔记-Main-守护进程Daemon(二)
- mjpg-streamer学习笔记6----输入通道--主要涉及函数
- mjpg-streamer学习笔记8------输出通道--相关函数
- mjpg-streamer学习笔记5----输入通道
- mjpg-streamer学习笔记1----mjpg-streamer移植
- mjpg-streamer学习笔记7------输出通道
- mjpg-streamer学习笔记10-----自己写客服端_编译调试
- mjpg-streamer学习笔记2----框架分析
- Mjpeg-streamer源码学习笔记-Main-守护进程Daemon(二)
- 孙鑫VC学习笔记:第十七讲 (一) 用剪贴板实现进程间的通信
- 孙鑫VC学习笔记:第十七讲 (三) 用命名管道实现进程间的通信
- 【Linux内核学习笔记】进程的创建过程
- WinApi学习笔记-创建进程
- windows api学习笔记-读写其他进程的内存
- Linux进程通信---学习笔记(二)
- 学习笔记 -- 进程控制 API
- 操作系统概念学习笔记一 进程
- HOOK SwapContext 枚举隐藏进程(学习笔记4)