进行编译
gcc –o mubiao mubiao.c
日志
周一
安装vm虚拟机,安装centos系统,验证实验一。
周二
二
2-2
sscanf
:: sscanf(argv[1], "%d", &nClone) ;
sscanf与scanf类似,都是用于输入的,只是后者以屏幕(stdin)为输入源,前者以固定字符串为输入源。
sprintf
sprintf(szCmdLine,"\"%s\" %d",szFilename,nCloneID);
将多个参数按一定格式存入另一个变量中
如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。
TCHAR
char是C语言bai标准数据类型,字符型,至于由du几个字节组成通常由zhi编译器决定,一般一个字节。Windows为了消除各dao编译器的差别,重新定义了一些数据类型。
CHAR为单字节字符。还有个WCHAR为Unicode字符,即不论中英文,每个字有两个字节组成。如果当前编译方式为ANSI(默认)方式,TCHAR等价于CHAR,如果为Unicode方式,TCHAR等价于WCHAR。
BOOL与bool
1、类型不同 : BOOL为int型 , bool为布尔型
2、长度不同 : bool只有一个字节 , BOOL长度视实际环境来定,一般可认为是4个字节
3、取值不同 : bool取值false和true,是0和1的区别; false可以代表0,但true有很多种,并非只有1。
4、bool表示布尔型变量,也就是逻辑型变量的定义符,以英国数学家、布尔代数的奠基人乔治·布尔(George Boole)命名。
int main(int argc, char* argv[] )
参数都是什么
第一个参数,int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数,在VS中默认值为1。
第二个参数,char型的argv[],为字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:
argv[0]指向程序运行的全路径名
argv[1]指向在DOS命令行中执行程序名后的第一个字符串
argv[2]指向执行程序名后的第二个字符串
argv[3]指向执行程序名后的第三个字符串
argv[argc]为NULL
第三个参数,char*型的env,为字符串数组。env[]的每一个元素都包含ENVVAR=value形式的字符串,其中ENVVAR为环境变量,value为其对应的值。平时使用到的比较少。
GetModuleFileName()
DWORD WINAPI GetModuleFileName(
_In_opt_ HMODULE hModule, //应用程序或DLL实例句柄,NULL则为获取当前程序可执行文件路径名
_Out_ LPTSTR lpFilename, //接收路径的字符串缓冲区
_In_ DWORD nSize //接收路径的字符缓冲区的大小
);
函数返回当前进程已加载可执行或DLL文件的完整路径名(以’\0’终止),该模块必须由当前进程地址空间加载。如果想要获取另一个已加载模块的文件路径,可以使用GetModuleFileNameEx()函数。
STARTUPINFO
STARTUPINFO用于指定新进程的主窗口特性的一个结构。
PROCESS_INFORMATION
① hProcess:返回新进程的句柄。
② hThread:返回主线程的句柄。
内核
内核是操作系统最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且内核决定一个程序在什么时候对某部分硬件操作多长时间。
内核对象
内核对象是系统提供的用户模式下代码与内核模式下代码进行交互的基本接口。
内核对象的数据结构只能被内核访问,因此应用程序无法在内存中找到这些数据结构并直接改变它们的内容。
当调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的句柄。
CloseHandle
关闭一个内核对象。其中包括文件、文件映射、进程、线程、安全和同步对象等。在CreateThread成功之后会返回一个hThread的handle,且内核对象的计数加1,CloseHandle之后,引用计数减1,当变为0时,系统删除内核对象。
若在线程执行完之后,没有调用CloseHandle,在进程执行期间,将会造成内核对象的泄露,相当于句柄泄露,但不同于内存泄露,这势必会对系统的效率带来一定程度上的负面影响。但当进程结束退出后,系统会自动清理这些资源
ZeroMemory
其作用是用0来填充一块内存区域
句柄
在Windows环境中,句柄是用来标识项目的。
句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制。
GetLastError
/// debug
DWORD dwRet = GetLastError();
if (hMutexSuicide)
{
if (ERROR_ALREADY_EXISTS == dwRet)
{
printf("程序已经在运行中了,程序退出!\n");
}
}
互斥
互斥量内核对象能够确保一个进程独占对一个资源的访问。互斥量与关键段(线程同步方式)的行为完全相同,当互斥量是内核对象,而关键段是用户模式下的的同步对象。
互斥量对象包含:一个线程ID,使用计数和递归计数。线程ID表示当前占用该互斥量的线程ID,递归计数表示该线程占用互斥量的次数,使用计数表示使用互斥量对象的不同线程的个数。
CreateMutex
作用是找出当前系统是否已经存在指定进程的实例。如果没有则创建一个互斥体。
OpenMutex
OpenMutex函数为现有的一个已命名互斥体对象创建一个新句柄。
ReleaseMutex
ReleaseMutex是一种线性指令,具有释放线程拥有的互斥体的控制权。
WaitForSingleObject
WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。
2-3
strcmp 小于为-1,等于为0,大于为1
实验三
概念
fork
fork系统调用用于创建一个新进程,称为子进程,它与进程(称为系统调用fork的进程)同时运行,此进程称为父进程。创建新的子进程后,两个进程将执行fork()系统调用之后的下一条指令。子进程使用相同的pc(程序计数器),相同的CPU寄存器,在父进程中使用的相同打开文件。
它不需要参数并返回一个整数值。下面是fork()返回的不同值。
负值 :创建子进程失败。
零 :返回到新创建的子进程。
正值 :返回父进程或调电者。该值包含新创建的子进程的进程ID
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
UNIX将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。在不同的UNIX (Like)系统下,我们无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。所以在移植代码的时候我们不应该对此作出任何的假设。
由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。
srand
rand函数在产生随机数前,需要系统提供的生成伪随机数序列的种子,rand根据这个种子的值产生一系列随机数。如果系统提供的种子没有变化,每次调用rand函数生成的伪随机数序列都是一样的。srand(unsigned seed)通过参数seed改变系统提供的种子值,从而可以使得每次调用rand函数生成的伪随机数序列不同,从而实现真正意义上的“随机”。通常可以利用系统时间来改变系统的种子值,即srand(time(NULL)),可以为rand函数提供不同的种子值,进而产生不同的随机数序列。
fprintf
int fprintf (FILE* stream, const char*format, [argument])
fprintf( )会根据参数format 字符串来转换并格式化数据,然后将结果输出到参数stream 指定的文件中,直到出现字符串结束(‘\0’)为止
exec
exec函数族提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行的脚本文件。
使用exec函数族主要有两种情况:
(1)当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用exec函数族中的任意一个函数让自己重生。
(2)如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用exec函数族中的任意一个函数,这样看起来就像通过执行应用程序而产生了一个新进程(这种情况非常普遍)。
exec函数族共有6种不同形式的函数。这6个函数可以划分为两组:
(1)execl、execle和execlp。
(2)execv、execve和execvp。
两组函数的不同在于exec后的第一个字符,第一组是l,在此称,为execl系列;第二组是v,在此称为execv系列。这里的l是list(列表)的意思,表示execl系列函数需要将每个命令行参数作为函数的参数进行传递;而v是vector(矢量)的意思,表示execv系列函数将所有函数包装到一个矢量数组中传递即可。
exec函数的原型如下:
int execl(const char * path,const char * arg,…);
int execle(const char * path,const char * arg,char * const envp[]);
int execlp(const char * file,const char * arg,…);
int execv(const char * path,char * const argv[]);
int execve(const char * path,char * const argv[],char * const envp[]);
int execvp(const char * file,char * const argv[]);
参数说明:
path:要执行的程序路径。可以是绝对路径或者是相对路径。在execv、execve、execl和execle这4个函数中,使用带路径名的文件名作为参数。
file:要执行的程序名称。如果该参数中包含“/”字符,则视为路径名直接执行;否则视为单独的文件名,系统将根据PATH环境变量指定的路径顺序搜索指定的文件。
argv:命令行参数的矢量数组。
envp:带有该参数的exec函数可以在调用时指定一个环境变量数组。其他不带该参数的exec函数则使用调用进程的环境变量。
arg:程序的第0个参数,即程序名自身。相当于argv[0]。
…:命令行参数列表。调用相应程序时有多少命令行参数,就需要有多少个输入参数项。注意:在使用此类函数时,在所有命令行参数的最后应该增加一个空的参数项(NULL),表明命令行参数结束。
返回值:一1表明调用exec失败,无返回表明调用成功
waitpid
pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid, int *stat_loc,int options);
当进程调用 wait,它将进入睡眠状态直到有一个子进程结束。wait 函数返回子进程的进程 id,
stat_loc 中返回子进程的退出状态。
waitpid 的第一个参数 pid 的意义:
pid > 0: 等待进程 id 为 pid 的子进程。
pid == 0: 等待与自己同组的任意子进程。
pid == -1: 等待任意一个子进程
pid < -1: 等待进程组号为-pid 的任意子进程。
因此,wait(&status)等价于 waitpid(-1, &status, 0),waitpid 第三个参数 option 可以是 0,WNOHANG,
WUNTRACED 或这几者的组合。
WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID。
WUNTRACED 若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。WIFSTOPPED(status)宏确定返回值是否对应与一个暂停子进程。
wait
wait(0)一般是父进程用来等待子进程用的,用来防止子进程成为僵尸进程,0表示父进程不关心子进程的终止状态
父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.
如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID 1)继承,当子进程终止时,init进程捕获这个状态.
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就像下面这样:
pid = wait(NULL);
如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
stderr
stdout(标准输出),输出方式是行缓冲。输出的字符会先存放在缓冲区,等按下回车键时才进行实际的I/O操作。
stderr(标准错误),是不带缓冲的,这使得出错信息可以直接尽快地显示出来。
3-1
mingyue@mingyue-PC:~/Desktop/code/code3$ gcc -o code3-1 code3-1.c
code3-1.c: In function ‘main’:
code3-1.c:6:21: warning: implicit declaration of function ‘time’ [-Wimplicit-function-declaration]
srand((unsigned)time(NULL));
^~~~
code3-1.c:7:14: warning: implicit declaration of function ‘fork’ [-Wimplicit-function-declaration]
while((x=fork())==-1)
^~~~
code3-1.c:11:13: warning: implicit declaration of function ‘sleep’ [-Wimplicit-function-declaration]
sleep(rand() % 2);
缺少头文件
#include
#include
#include
#include
3-2
#include
#include
#include
#include
实验四
概念
消息队列
消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。
消息队列存放在内核中,只有重启内核(即操作系统重启)或者显示地删除一个消息队列时,该消息队列才会真正删除。
exit
return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
0777
表示队列,组用户、当前用户以及其它用户的读写权限
实验五
概念
createMutex
lpMutexAttributes | SECURITY_ATTRIBUTES,指定一个SECURITY_ATTRIBUTES结构,或传递零值(将参数声明为ByVal As Long,并传递零值),表示使用不允许继承的默认描述符 |
---|---|
bInitialOwner | Long,如创建进程希望立即拥有互斥体,则设为TRUE。一个互斥体同时只能由一个线程拥有 |
lpName | String,指定互斥体对象的名字。用vbNullString创建一个未命名的互斥体对象。如已经存在拥有这个名字的一个事件,则打开现有的已命名互斥体。这个名字可能不与现有的事件、信号机、可等待计时器或文件映射相符 |
CreateSemaphore
创建一个新的信号机
Long,如执行成功,返回信号机对象的句柄;零表示出错。会设置GetLastError。即使返回一个有效的句柄,但倘若它指出同名的一个信号机已经存在,那么GetLastError也会返回ERROR_ALREADY_EXISTS
参数 | 类型及说明 |
---|---|
lpSemaphoreAttributes | SECURITY_ATTRIBUTES,指定一个SECURITY_ATTRIBUTES结构,或传递零值(将参数声明为ByVal As Long,并传递零值)——表示采用不允许继承的默认描述符。该参数定义了信号机的安全特性 |
lInitialCount | Long,设置信号机的初始计数。可设置零到lMaximumCount之间的一个值 |
lMaximumCount | Long,设置信号机的最大计数 |
lpName | String,指定信号机对象的名称。用vbNullString可创建一个未命名的信号机对象。如果已经存在拥有这个名字的一个信号机,就直接打开现成的信号机。这个名字可能不与一个现有的互斥体、事件、可等待计时器或文件映射的名称相符 |
一旦不再需要,一定记住用CloseHandle关闭信号机的句柄。它的所有句柄都关闭以后,对象自己也会删除
一旦值大于零,信号机就会触发(发出信号)。ReleaseSemaphore函数的作用是增加信号机的计数。如果成功,就调用信号机上的一个等待函数来减少它的计数
createThread
lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,NULL使用默认安全性,不可以被子线程继承,否则需要定义一个结构体将它的bInheritHandle成员初始化为TRUE
dwStackSize,设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈的大小。
lpStartAddress,指向线程函数的指针,形式:@函数名,函数名称没有限制,
线程有两种声明方式
(1)DWORD WINAPI 函数名 (LPVOID lpParam); //标准格式
DWORD WINAPI 函数名 (LPVOID lpParam)
{
return 0;
}
CreateThread(NULL, 0, 函数名, 0, 0, 0);
(2)void 函数名();
使用void 函数名()此种线程声明方式时,lpStartAddress需要加入LPTHREAD_START_ROUTINE转换,如
void 函数名()
{
return;
}
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)函数名, 0, 0, 0);
lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
dwCreationFlags :线程标志,可取值如下
(1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程,
(2)0:表示创建后立即激活。
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈 的大小,否则,dwStackSize指定提交的大小。该标记值在Windows 2000/NT and Windows Me/98/95上不支持。
lpThreadId:保存新线程的id。
返回值:函数成功,返回线程句柄;函数失败返回false。若不想返回线程ID,设置值为NULL。
函数说明:
创建一个线程。
语法:
hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ;
一般并不推荐使用 CreateThread函数,而推荐使用RTL库里的System单元中定义的 BeginThread函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施。
在MFC程序中,应该调用AfxBeginThread函数,在[Visual C++](https://baike.baidu.com/item/Visual C%2B%2B)程序中应调用_beginthreadex函数。
实验六
概念
LPCVOID
LPCVOID 就是一个常 void 类型指针,可以指向任何类型
其中的 C 就是 const 的缩写
DWORD
由 4 字节长(32 位整数)的数字表示的数据
setw(int n)
setw(int n)用来控制输出间隔,setw()只对其bai后面紧跟du的输出产生作用,不足的用空格填充。若输入的内容超过setw()设置的长度,则按实际长度输出。