Linux学习笔记

Linux学习笔记

        文章主要介绍了Linux系统编程方面的基础知识,以及Linux的一些基本概念。内容较为浅显,并且不全,会随着学习的深入逐渐丰富完善。文中部分内容参考网络,若文中内容存在错误之处,欢迎大家指正。:)

第1章 工具篇

1.1 gcc

1.2 makefile

第2章 文件篇

2.1 Linux目录结构

图2.1 Linux根目录

图2.1 Linux根目录(用“/”表示)结构

根目录内容(这里只介绍几个常用的):
(1)media: 用于挂载光盘,U盘和DVD等设备
(2)usr: 系统存放程序的目录,类似于在windows下的文件夹programefiles
(3)var: 存放内容常变动的文件目录,如系统日志文件
(4)root: root用户的家目录
(5)bin: 存放系统命令的目录,如命令cat,cp,mkdir
(6)dev: 所有设备文件的目录(如声卡、硬盘、光驱)
(7)etc: 系统的主要配置文件
(8)home: 用户家目录数据的存放目录
(9)mnt: 同media作用一样,用于临时挂载存储设备
在根目录下使用tree -L 1(只显示1级)可以查看到根目录下的目录结构:

图2.2 根目录下的目录结构

(本节内容大部分摘自:[ 野火]i.MX Linux开发实战指南

2.2 Linux文件权限

2.2.1 文件权限

Linux系统中文件属性有:可读权限(r)、可写权限(w)、可执行权限(x)。通过“ll”命令可查看到一个文件的属性:
图2.3 查看文件权限

图2.3 查看文件权限
文件权限对于文件和目录来说是不同的:
rwx
文件文件内容可被查看(可以使用cat、less等)文件内容可被修改(可使用vim、>等)文件可被执行(可以运行:./文件名)
目录目录可以被浏览(可使用ls,tree等)目录内的文件可以被修改、删除、创建(可使用mv、touch等)目录可以被打开(可使用cd等)
2.2.2 文件默认权限—umask

umask的作用是:设置当前进程下新建的文件和目录的默认权限
umask设置的是权限“补码”。如图2.4所示umask值为002,则对应新建的目录(kk)权限为7-0=7,7-0=7,7-2=5,即用777减去umask的相应位上的值;而对应的新建的文件(main)权限是用666减去umask的相应位上的值。
图2.4 umask的作用

图2.4 umask的作用

下面通过一个例子看一下,umask在进程中的影响。

#include <stdio.h>

int main(){
	system("touch test0.txt");
	system("mkdir folder0");
	umask(022);
	system("touch test1.txt");
	system("mkdir folder1");
	umask(066);
	system("touch test2.txt");
	system("mkdir folder2");
	system("ls -l");
	return 0;
}

测试结果:

[taoge@localhost learn_c]$ umask 000
[taoge@localhost learn_c]$ gcc test.c
[taoge@localhost learn_c]$ . / a.out
total 24
- rwxrwxrwx 1 taoge taoge 4925 May  5 07:43 a.out
drwxrwxrwx 2 taoge taoge 4096 May  5 07 : 43 folder0
drwxr - xr - x 2 taoge taoge 4096 May  5 07 : 43 folder1
drwx--x--x 2 taoge taoge 4096 May  5 07 : 43 folder2
- rw - rw - rw - 1 taoge taoge    0 May  5 07 : 43 test0.txt
- rw - r--r-- 1 taoge taoge    0 May  5 07 : 43 test1.txt
- rw------ - 1 taoge taoge    0 May  5 07 : 43 test2.txt
- rwxrw - rw - 1 taoge taoge  279 May  5 07 : 42 test.c
[taoge@localhost learn_c]$ umask
0000
[taoge@localhost learn_c]$

从以上结果可以看出:
(1)根据test0.txt可知,子进程会继承父进程的默认屏蔽字。
(2)根据test1.txt可知,子进程可以改变自己的默认屏蔽字。
(3)根据最后shell进程的屏蔽字为000可知,子进程不会改变父进程的屏蔽字。

(本节内容部分摘自:linux中umask命令/函数的用法简介

2.4 软链接与硬链接

软链接:相当与windows系统中的快捷方式,可以对某个文件或目录创建软链接,然后通过该软链接访问相应的文件或目录。
可以用vim查看软链接的具体内容:里面有原文件的文件名和路径。因此在创建软链接时,最好使用绝对路径创建。若使用相对路径则当软链接移动到其他目录时,很可能就不能通过该软链接找到相应的原文件了。
图2.5 硬链接(1)

图2.5 硬链接(1)

图2.5 硬链接(2)

图2.5 硬链接(2)
硬链接:只能对文件创建硬链接,目录不行。在创建硬链接时,不需要用绝对地址,即使硬链接发生移动也能访问到原文件。硬链接的更多介绍请看2.4节。

图2.6 不能对目录创建硬链接

图2.6 不能对硬件创建硬链接

2.4 目录项与inode

目录项(dentry):其本质是结构体,重要成员变量有两个 {文件名, inode, …},而文件内容(data)保存在磁盘盘块中。
inode:本质是一个结构体。用于存储文件的属性信息,如:权限、类型、大小、时间、用户、盘块位置(文件在磁盘中的存储位置)……也叫作文件属性管理结构,大多数的 inode 都存储在磁盘上。少量常用、近期使用的 inode 会被缓存到内存中。ls -l查看到的信息都可以在inode中查看到。用stat命令可以查看到文件的inode号。
下面我们为main.c创建多个硬链接,然后在查看一下各个硬链接的inode。
图2.7 为mian.c创建硬链接

图2.7 为mian.c创建硬链接.png

图2.8 查看inode(1)

图2.8 查看inode(1)

图2.8 查看inode(2)

图2.8 查看inode(2)
可以看出三个硬链接main1、main2、main3的inode和原文件main.c一样。由于Inode记录了文件的盘块位置等信息。我们在main.c中输入以下内容:
//file:main.c                                      
#include<stdio.h>                                                  
int main(int argc, char* argv[])                                    
{                                                                     
	return 0;                                                         
}    

然后删除main.c,看能否还能通过硬链接来读出输入的内容:

图2.9 通过硬链接查看文件

图2.9 通过硬链接查看文件

可以看出依旧可以通过硬链接访问到输入的内容。下面我们在看一下硬链接、dentry、inode之间的关系:

图2.10 inode与目录项直接的关系

图2.10 inode与目录项之间的关系

​ 从图中可以看出,所有的硬链接都共用一个inode,它们之间只是目录项不同。事实上在删除一个文件时,操作系统并没有将文件存储在磁盘上的内容擦除掉,而只是将文件的目录项删除了,当一个inode的Links=0时,系统就认为存放该文件的磁盘区域可覆盖,该区域的内容随时可能被其他内容覆盖。

2.5 重定向

2.5.1 输出重定向
> #(输出重定向):会将原来的文件的内容覆盖。若重定向的文件不存在,则会创建该文件。
>> #(追加): 不会覆盖原来文件的内容,而是追加到文件的尾部。

例如:
main.cpp和main.c文件内容如下

图2.11main.cpp和main.c文件内容

图2.11main.cpp和main.c文件内容

执行下面命令:

linux@ubuntu:~/ test$ echo "// test" > main.cpp                  
linux@ubuntu:~/ test$ echo "// test" >> main.c   

通过vim在查看main.c和main.cpp,结果如下:

图2.12查看main.c和main.cpp,结果

图2.12查看main.c和main.cpp,结果

2.6 管道

管道是一种通信机制,通常用于进程间的通信(也可通过socket进行网络通信),它表现出来的形式是将前面每一个进程的输出(stdout)直接作为下一个进程的输入(stdin)。例如:

linux@ubuntu:~/ share / dup$ cat dup2.c | grep 'brief'                   
* brief : 利用dup2()实现输出重定向   

在进程篇的进程通信一节会更详细的介绍管道。

2.7 Linux文件操作相关函数

//打开或创建一个文件,并返回一个文件描述符
int open(const char *pathname, int flags, ...);
//关闭一个已打开的文件
int close(int fd);  
//从打开的设备或文件中读取数据
ssize_t read(int fd, void *buf, size_t count); 
//向打开的设备或文件中写数据
ssize_t write(int fd, const void *buf, size_t count); 
//错误处理函数
void perror(const char *s);                           
char *strerror(int errnum);     
//和fseek()类似,可以修改当前读写位置(偏移量)
off_t lseek(int fd, off_t offset, int whence);
//改变一个已打开的文件的属性
int fcntl(int fd, int cmd, ...);    
//获取文件属性(从 inode 结构体中获取)
int stat(const char *path, struct stat *buf);  
//截断文件长度成指定长度。常用来拓展文件大小,代替 lseek
int truncate(const char *path, off_t length);   
//可以为已经存在的文件创建目录项(硬链接)
int link(const char *oldpath, const char *newpath); 
//删除一个文件的目录项
int unlink(const char *pathname);  
//获取进程当前工作目录
char *getcwd(char *buf, size_t size);  
//改变当前进程的工作目录
int chdir(const char *path);  
//根据传入的目录名打开一个目录
DIR *opendir(const char *name); 
//关闭打开的目录
int closedir(DIR *dirp);   
//读取目录
struct dirent *readdir(DIR *dirp);
//文件描述符拷贝
int dup(int oldfd);   
//文件描述符拷贝。 重定向文件描述符指向
int dup2(int oldfd, int newfd);  

第3章 进程篇

3.1 相关概念

本节主要介绍进程中常常提到的一些概念。
(1)并发与并行
并发:一个处理器同时处理多个任务。
并行:多个处理器或者是多核的处理器同时处理多个不同的任务。
前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生

(2)MMU
内存管理单元。MMU位于CPU内,主要用于完成虚拟地址到物理地址的映射,此外MMU还可以用于修改内存的访问级别(避免用户空间随意范围内核空间)。

(3)进程共享
父子进程间遵循读时共享写时复制的原则。父子进程间共享的内容有:1、文件描述符(打开文件的结构体); 2、mmap 建立的映射区。注意父子进程间不共享全局变量

(4)僵尸进程和孤儿进程
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为 init进程,称为 init 进程领养孤儿进程。
僵尸进程: 进程终止,父进程尚未回收, 子进程残留资源(PCB)存放于内核中, 变成僵尸(Zombie) 进程。
注意,僵尸进程是不能使用 kill 命令清除掉的。因为 kill 命令只是用来终止进程的,而僵尸进程已经终止。用什么办法可清除掉僵尸进程呢?可以选择杀死父进程,只要父进程死了,子进程就会变为孤儿进程被init进程回收、杀死。
(5)参考程序


/**
* file: test.c
* brief: 通过waitpid()来观察阻塞和非阻塞,当使用WNOHANG时为非阻塞
*
* author:
*/

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

void main(void){
	int pid = -1;
	int i = 0;
	int StatusProcessEixt = 0;
	for (i = 0; i < 5; i++){
		pid = fork();
		if (pid == -1){
			perror("fork() error.");
			i--;
		}
		else if (pid == 0)    //son process need break.
			break;
		else if (pid > 0)
			continue;
	}
	printf("parent id:%d, myself id:%d, i = %d\n", getppid(), getpid(), i);
	sleep(1);
	if (i == 2){
		while (1);  //i == 2 的这个子进程将不会退出,若使用WNOHANG,则该子进程最终会变为孤儿进程
	}
	if (i == 5){ // parent process waitpid
		for (i = 0; i < 5; i++){
			pid = waitpid(-1, &StatusProcessEixt, WNOHANG);
			if (pid >0)
				printf("%d exit\n", pid);
			else if (pid == 0){
				printf("son process is run!\n");
				// i--;
				// sleep(1);
			}
			else printf("no son process! \n");
		}
		printf("main:%d will exit :-)\n", getpid());
	}
	sleep(5);
}

结果:如下图所示,最终有一个子进程变为了孤儿进程

图3.1 一个子进程变为了孤儿进程

图3.1一个子进程变为了孤儿进程

(本节内容大部分摘自:黑马程序员-Linux系统编程教程)

3.2 进程间通信

进程间的通信方式主要有:管道、mmap、信号。下面主要介绍管道和mmap通信方式。

3.2.1 管道

管道具有以下特性:
1、其本质是一个伪文件(实为内核缓冲区,伪文件不占用磁盘空间)
2、由两个文件描述符引用,一个表示读端,一个表示写端。
3、规定数据从管道的写端流入管道,从读端流出。
4、管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

pipe实现父子进程间通信,参考程序:

/**
* file: test.c
* brief: 测试父子进程间管道通信:父进程写,子进程读。
* note:程序并没有将父进程的读端和子进程的写端关掉,应该是要关掉的
*       可为什么一定要关掉呢?一种解释:管道的读写端通过打开的文件描述符来
        传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道文件描述
		符。上面的例子是父进程把文件描述符传给子进程之后父子进程之间通信,
		也可以父进程fork两次,把文件描述符传给两个子进程,然后两个子进程之
		间通信,总之需要通过fork传递文件描述符使两个进程都能访问同一管道,它们才能通信。
* author:
*/

#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include<stdlib.h>

#define SONNUM 5              //子进程个数为:SON-1
#define STR "I love you!"     //向管道内写入的内容
#define STR_MAX 20            //STR的最大长度

void main(void){
    int pid = -1;
	int i = 0;
    int pipeFd[2];    //其中pipeFd[0]为读端,pipeFd[1]为写端
    
	if(pipe(pipeFd) == -1){    //创建一个管道
	    perror("pipe() is error");
		exit(1);
	}
    
    for(i = 1; i < SONNUM;i++){   //创建子进程
	    pid = fork();
		if(pid == -1){
		    perror("fork() is error");
			i--;    //若创建失败,就在创建一次
		}
		else if(pid == 0){    //son process
		    printf("son process: %d : pid: %d\n", i, getpid());
		    break;
		}
		else if(pid > 0){    //parent process
		  write(pipeFd[1], STR, sizeof(STR));    //每次创建一个子进程都会向管道写内容
		  sleep(1);
		}
	}
	
    if(i == SONNUM){    //parent process
	    int status;
		for(i = 1; i < SONNUM; i++){
			printf("wait() ret is: %d\n",wait(&status));    //等待子进程结束
			printf("status is %d\n",WIFSTOPPED(status));
		}
	    printf("parent process:I will exit :-)\n");
	}
	else{     //子进程读管道
		char buf[STR_MAX];
	    read(pipeFd[0], buf, STR_MAX);
		printf("%d: pid: %d,:I'm receive: %s -QWQ- I will not exit :-)\n", i, getpid(),buf);
		}
}

结果:

图3.1 一个子进程变为了孤儿进程

图3.2 pipe实现父子进程间通信

FIFO命名管道实现两进程间通信,参考程序:

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>

#define FIFO_PATH "./FIFO"    //文件FIFO的存放路径

int main(void){
	int fd, pid = -1;
    
	if(-1 == mkfifo(FIFO_PATH, 0664)){
	    perror("mkfifo() is error");
		exit(1);
	}

	pid = fork();
	if(pid == -1){
	    perror("fork() is error");
		exit(1); 
	}
	else if(pid == 0){  //son process 
        char bufWR[12] = {"hello word!"};
        fd = open(FIFO_PATH, O_WRONLY);
	    write(fd, bufWR,12 );
	    close(fd);
		sleep(1);
	}
	else if(pid > 0){
	    char bufRD[12];
        fd = open(FIFO_PATH, O_RDONLY);
	    read(fd, bufRD, 12);
		printf("I'm parent! read:%s\n", bufRD);
	    close(fd);
        wait(NULL);
	}
    return 0;
}
3.2.2 mmap

存储映射 I/O (Memory-mapped I/O) :使一个磁盘文件与存储空间中的一个缓冲区相映射。存储映射I/O是一种基于内存区域的高级I/O操作,它将磁盘文件与进程地址空间中的一个内存区域相映射。当从这段内存中读数据时,就相当于读磁盘文件中的数据,将数据写入这段内存时,则相当于将数据直接写入磁盘文件。这样就可以在不使用基本I/O操作函数read和write的情况下执行I/O操作,这个映射工作可以通过 mmap 函数来实现。

3.3 会话与守护进程

会话是多个进程组的集合。守护进程是 Linux 中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以 d 结尾的名字。守护进程运行在操作系统后台,不受用户登录和注销影响。

什么是进程的当前工作目录呢?

任何进程的当前工作目录都用于解释相对路径。例如,如果您的外壳程序的当前工作目录为,/home/rene并且您是ls …从外壳程序运行的,则该进程的当前工作目录/home/rene将用于解析…为/home。

创建守护进程示例:

//file: daemon.c 
//brief: 守护进程测试
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define WORK_PATH "/"   //会话的当前目录

int main(int argc, char* argv[])
{
	pid_t pid, sessionID;
	int fd;
    //1、创建子进程
	pid = fork();
	//2、父进程退出
	if(pid > 0)    //父进程
	{
		printf("\nparent: pid is %d,gid is %d, son pid is %d\n",getpid(), getpgid(0),pid);
		sleep(1);
		printf("\nparent: I will exit :-)\n");
        exit(0);
	}
	else if(pid == 0)    //子进程
	{
        printf("\nson:  pid is %d,gid is %d, ppid is %d\n",getpid(), getpgid(0),getppid());
		sleep(3);    //睡眠3s,等父进程完全退出
        printf("\nson:  pid is %d,gid is %d, ppid is %d\n",getpid(), getpgid(0),getppid());
	}
	else
	{
	    perror("fork() error.");
		exit(1);
	}
	//3、在子进程中创建新会话
    sessionID = setsid();
	if(sessionID == -1)
	{
	    perror("setsid() error.");
		exit(1);
	}
	printf("\nson: create session success!\n");
	printf("\nson: pid is %d, ppid is %d, gid is %d, sessionID is %d\n",getpid(),getppid(),getpgid(0),getsid(0));
    //4、改变当前目录
	if(chdir(WORK_PATH) < 0)
	{
	    perror("chdir() error.");
		exit(1);
	}
	//5、设置文件权限掩码
	umask(0);
    //6、关闭STDOUT、STDIN、STDERR三个文件描述符
    fd = open("/dev/null", O_RDWR);
	dup2(fd , STDOUT_FILENO);
	dup2(fd , STDERR_FILENO);
	dup2(fd , STDIN_FILENO);
	//7、开始执行守护进程的核心工作
	while(1)
	{
	    sleep(1);
	}

    return 0;
}

(本节内容部分摘自:每个进程都有一个当前目录是什么意思?)

3.4 Linux进程相关函数

//获取环境变量值
char *getenv(const char *name);
//设置环境变量的值
int setenv(const char *name, const char *value, int overwrite);
//删除环境变量 name 的定义
int unsetenv(const char *name);
//创建一个子进程
pid_t fork(void);
//获取当前进程 ID
pid_t getpid(void);
//获取当前进程的父进程 ID
pid_t getppid(void);
//获取当前进程实际用户 ID
uid_t getuid(void);
//获取当前进程使用用户组 ID
gid_t getgid(void);
//子进程往往要调用一种 exec 函数以执行另一个程序
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
//阻塞等待子进程退出,并回收子进程残留资源和获取子进程结束状态(退出原因)。
pid_t wait(int *status);
//作用同 wait,但可指定 pid 进程清理,可以不阻塞。
pid_t waitpid(pid_t pid, int *status, in options);
//创建管道
int pipe(int pipefd[2]);
//创建一个 FIFO
int mkfifo(const char *pathname, mode_t mode);
//存储映射 I/O
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
//释放mmap 建立的映射区
int munmap(void *addr, size_t length);

第4章 线程篇

4.1相关概念

1、线程之间共享和非共享的资源

2、进程属性设置

3、进程同步的几种方式:互斥锁、读写锁、条件变量、信号量

4、什么时候会出现死锁

4.2 示例

4.2.1 互斥锁
// file: main.c
// brief: 互斥锁测试程序
#include<stdio.h>
#include <pthread.h>
#include <string.h>
#include<errno.h>
#include <stdlib.h>

pthread_mutex_t mutex;    //互斥量
/**
 * brief: 子线程处理函数
 * param: 无
 * return: 无
 * note: 这里终端(shell)相当于互斥资源,通过互斥锁使得每个线程在打印自己的信息时不会出现混乱
 */
void *pthreadDeal(void *arg)
{
	//3、加锁
	pthread_mutex_lock(&mutex);
	printf("%lu: I", pthread_self());
	sleep(2);
	printf(" will exit!\n");
	//4、解锁
	pthread_mutex_unlock(&mutex);
	pthread_exit(NULL);

    return NULL;
}

int main(){
    int ret, i = 0;
	pthread_t pthreadID[5];

    //1、初始化互斥锁
	pthread_mutex_init(&mutex, NULL);
	//2、创建子线程
	for(i = 0; i <5; i++)
	{
	    ret = pthread_create(&pthreadID[i], NULL, pthreadDeal, NULL);
    	if(ret != 0)  //
        {
	        printf("pthread_create() error. %s\n", strerror(errno));
	    	exit(1);
       	}
	}
	//5、等待子线程退出
	for(i = 0; i <5; i++)
	{
    	if(pthread_join(pthreadID[i], NULL) != 0)
    	{
	        printf("pthread_join() error. %s", strerror(errno));
	    	exit(1);
    	}
	}
	printf("%lu : I will exit!\n", pthread_self());
	//6、销毁互斥锁
	pthread_mutex_destroy(&mutex);               
    return 0;
}
4.2.2 条件变量
//file: condLock.c
//brief: 条件变量测试程序
//note: 本程序通过条件变量实现了一个简单的消费者-生产者模型
#include<string.h>
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>

pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;    //初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;        //初始化互斥量
struct msg{            //生产者生产的产品(链表结构)
    struct msg *next;
	int num;
};
struct msg *head;      //产品的头结点

//brief: 生产者线程
void *productor(void *arg){
    struct msg *mp;        
	while(1){
    	mp = (struct msg*)malloc(sizeof(struct msg));
		mp->num = rand() % 1000 + 1;

        pthread_mutex_lock(&mutex);
		mp->next = head->next;
		head->next = mp;
	    pthread_mutex_unlock(&mutex);
		printf("-productor -- %d\n", mp->num);

		pthread_cond_signal(&has_product);
		sleep(rand()%5);
	}
	return NULL;
}
//brief: 消费者线程
void *consumer(void *arg){
	struct msg *mp;
    while(1){
		pthread_mutex_lock(&mutex);
	    if(head->next == NULL)
			pthread_cond_wait(&has_product, &mutex);
		mp = head->next;
		head->next = mp->next;
		pthread_mutex_unlock(&mutex);
		printf("-consumer -- %d\n", mp->num);
		free(mp);
	}

    return NULL;
}

int main(int argc, char *argv[]){
    int  ret;
	pthread_t threadID[2];
    
    head = (struct msg*)malloc(sizeof(struct msg));    //为产品头结点分配空间
	head->next = NULL;
	head->num = 0;
	ret = pthread_create(&threadID[0], NULL, productor, NULL);   //创建生产者线程
	if(ret != 0)
		printf("pthread_create() error. %s", strerror(errno));
	ret = pthread_create(&threadID[1], NULL, consumer, NULL);    //创建消费者线程
	if(ret != 0)
		printf("pthread_create() error. %s", strerror(errno));
     
	pthread_join(threadID[0], NULL);    //回收生产者线程
	pthread_join(threadID[1], NULL);    //回收消费者线程
	
	return 0;
}
4.2.3 信号量
//file: sem.c
//brief: 信号量测试程序
//note: 程序中使用sem_trywait进行加锁,如果改为sem_wait程序会出现一点问题:
//      在testTime==0时可能消费者或生产者一直出于阻塞状态,程序卡死。
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>

int NUM = 5;
sem_t blackNum, productNum;
int testTime = 10;                //子线程运行时间:s
struct production{                //产品
	struct production *next;
	int num;
} ;
struct production *food;          //生产者生产的食物

void *consumer(void *arg){  
	struct production *e;
	
	printf("\nconsumer: working\n");
	while(testTime > 0){
		e = food->next;
        if(sem_trywait(&productNum) != 0)
			continue;
		food->next = food->next->next;
		sem_post(&blackNum);
		free(e);
		printf("\nconsumer: ==================\n");
		sleep(rand()%1);
	}
	printf("\nconsumer: ---exit----\n");
	pthread_exit(NULL);
	
	return NULL;
}

void *productor(void *arg){
	struct production *e;
	
	printf("\nproductor: working\n");
	while(testTime > 0){
	    e = (struct production *)malloc(sizeof(struct production));   //生产一个食物
		e->next = food->next;
		e->num = (rand() % 100) + 1;
		if(sem_trywait(&blackNum) != 0)
		{
			free(e);
			continue;
		}
		food->next = e;
		//打印产品
		printf("\nproductor: ==================\n");
		while(e != NULL)
		{
		    printf("%d--",e->num);
			e = e->next;
		}
		sem_post(&productNum);
		//睡眠
		sleep(rand()%2);
	}
	printf("\nproductor: ---exit----\n");
	pthread_exit(NULL);
	
	return NULL;
}

int main(int argc, char *atgv[]){
	int ret;
	pthread_t threadId[2];
	//初始化食物链条的头节点
	food = (struct production *)malloc(sizeof(struct production)); 
	food->next = NULL;
	food->num = 0;
	
	ret = sem_init(&blackNum, 0, NUM);
	ret = sem_init(&productNum, 0, 0);
	ret = pthread_create(&threadId[0], NULL, consumer, NULL);
	ret = pthread_create(&threadId[1], NULL, productor, NULL);
	while(testTime > 0) {
		sleep(1);
		testTime--;
	}
	ret = pthread_join(threadId[0], NULL);
	ret = pthread_join(threadId[1], NULL);
	ret = sem_destroy(&blackNum);
	ret = sem_destroy(&productNum);
	free(food);
	printf("\nmain: ---exit----\n");
	return 0;
}

4.3 Linux线程相关函数

//获取线程 ID
pthread_t pthread_self(void);
//创建一个新线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
//将单个线程退出
void pthread_exit(void *retval);
//阻塞等待线程退出,获取线程退出状态
int pthread_join(pthread_t thread, void **retval);
//实现线程分离
int pthread_detach(pthread_t thread);
//杀死(取消)线程
int pthread_cancel(pthread_t thread);
//初始化线程属性
int pthread_attr_init(pthread_attr_t *attr);
//销毁线程属性所占用的资源
int pthread_attr_destroy(pthread_attr_t *attr);
//设置线程属性,分离 or 非分离
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
//获取程属性,分离 or 非分离
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
//互斥锁
//读写锁
//条件变量
//信号量

第5章 网络篇

5.1 tcp三次握手四次挥手

图5.1 tcp三次握手四次挥手

图5.1 tcp三次握手四次挥手

其中:
SYN为同步标志,ACK为确认标志,FIN为结束标志,seq为序号,ack为确认序号。(更加详细的解释可以参考:TCP协议详解

注意几个概念:半关闭、2MSL

5.2 示例

5.2.1 socket模型
//file: Server.c 
//brief: 服务器端
//note: 服务器端接收客户端传来的字符串,并将字符串转化为大写传回给客服端
#include<stdio.h>
#include<unistd.h>
#include<strings.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netinet/in.h>

#define SERVER_PORT 6666        //服务器端的端口地址

//brief: 打印错误信息并让程序退出
void sysError(char *str){
    perror(str);
    exit(1);
}

int main(void){
    int sFd, cFd;                             //sFd:监听文件描述符; cFd:建立连接的文件描述符
    int n, i;
    char buf[80], str[INET_ADDRSTRLEN];
    socklen_t clientAddrLen;
    struct sockaddr_in serverAddr, clientAddr;
	
    //1、socket(), 打开网络通讯端口,创建一个套接字
    sFd = socket(AF_INET, SOCK_STREAM, 0);
	//2、将strcut sockaddr 清零
    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	//3、将参数sockfd和addr绑定在一起
    if(bind(sFd, (const struct sockaddr *) &serverAddr, sizeof(serverAddr)) == -1)
        sysError("server: bind is error!\n");
	//4、设置同时与服务器建立连接的上限数(同时进行3次握手的客户端数量)
    if(listen(sFd, 5) == -1)
        sysError("server: listen is error!\n");

    while(1){
        clientAddrLen = sizeof(clientAddr);
		//5、阻塞等待客户端建立连接,若成功,返回一个与客户端成功连接的socket文件描述符
        cFd = accept(sFd, (struct sockaddr *) &clientAddr, &clientAddrLen);
		//6、数据传输
        n = read(cFd, buf, 80);
        printf("receive from %s at PORT %d :%s\n",
                inet_ntop(AF_INET, &clientAddr.sin_addr, str, sizeof(str)),
                ntohs(clientAddr.sin_port),buf);
        for(i = 0; i < n; i++)
            buf[i] = toupper(buf[i]);
        write(cFd, buf, n);
		//7、关闭连接
        close(cFd);
    }
    
    return 0;
}
//file: Client.c 
//brief: 客户端
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERVER_PORT 6666        //服务器端的端口地址
#define SERVER_IP "127.0.0.1"   //服务器端的IP地址

//brief: 打印错误信息并让程序退出
void sysError(char *str){
    perror(str);
    exit(1);
}

int main(void){
    int fd;
    int n, i;
    char buf[80], str[INET_ADDRSTRLEN];
    socklen_t clientAddrLen;
    struct sockaddr_in clientAddr;

    //1、socket(), 打开网络通讯端口,创建一个套接字
    fd = socket(AF_INET, SOCK_STREAM, 0);
	//2、将strcut sockaddr 清零
    bzero(&clientAddr, sizeof(clientAddr));
    clientAddr.sin_family = AF_INET;
    clientAddr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &clientAddr.sin_addr);
	//3、与服务器建立连接
    if(connect(fd, (struct sockaddr *) &clientAddr, sizeof(clientAddr)) == -1)
        sysError("client: connect is error ");
	printf("please input something:");
    scanf("%s",str);
	//4、数据传输
    write(fd, str, strlen(str)+1);
    n = read(fd, buf, 80);
    printf("Response from server:%s\n",buf);

	//关闭连接
    close(fd);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值