Linux应用开发

Linux平台c语言应用开发

1. linux文件底层系统调用

  • open 获取一个文件描述符
  • write
  • read
  • close
  • 操作不区分文本,二进制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int main(int argc, char *argv[], char *envp[])
{
/*
int fd = open("file.txt", O_WRONLY | O_CREAT, 0600);
assert( fd != -1);
printf("fd=%d\n", fd);

write(fd,"hello", 5);
close(fd);

int fd = open("file.txt", O_RDONLY);
assert( fd != -1);
char buff[128] = {0};
int n = read(fd, buff, 127);
printf("n = %d,buff = %s\n", n, buff);
close(fd);
*/

int fdr = open("1.png", O_RDONLY);
int fdw = open("2.png", O_WRONLY | O_CREAT, 0600);
assert( fdr != -1 && fdw != -1);
char buff[256] = {0};
int num = 0;
while( (num = read(fdr, buff, 256)) > 0 )
{
write(fdw, buff, num);
}
close(fdw);
close(fdr);
exit(0);
}

2. 父子进程共享打开文件

  • 父子进程共享文件偏移量
  • 子进程可以访问父进程的文件
  • 多个进程指向同一个struct file,需要在每个文件close
  • 若先fork后打开文件,不会指向同一个struct file,每个进程有自己独立的struct file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(int argc, char *argv[], char *envp[])
{
int fd = open("file.txt", O_RDONLY);
assert(fd != -1);
pid_t pid = fork();
assert(pid != -1);

if( pid == 0)
{
char buff[128] = {0};
int n = read(fd, buff, 1);
printf("child %s\n", buff);
sleep(1);
n = read(fd, buff, 1);
printf("child %s\n", buff);
}else
{
char buff[128] = {0};
int n = read(fd, buff, 1);
printf("parent %s\n", buff);
sleep(1);
n = read(fd, buff, 1);
printf("parent %s\n", buff);
}
close(fd);
exit(0);
}

3. 系统调用和库函数区别

  • 系统调用在内核中,属于内核空间;库函数的实现在库函数中,属于用户空间
  • 某些库函数底层其实仍然是系统调用

4. 内存申请与释放

  • malloc()申请1G内存能否成功?
    • malloc(单位是b)
    • malloc申请的内存是虚拟内存,在实际使用的时候才会划分物理空间
    • 分配1G能分配成功,申请内存数量应该小于剩余内存(memory+swap)
  • 申请的内存如果没有释放会怎么样?
    • 在程序退出时,内存依旧会被回收

5. exec进程替换

  • linux系统交互都是一个fork和一个exec
  • execl(const char *path, const char arg, … / (char *) NULL */);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char *argv[], char *envp[])
{
printf("main pid = %d ppid = %d\n", getpid(), getppid());
pid_t pid = fork();
assert(pid != -1);

if(pid == 0)
{
printf("child pid=%d ppid=%d\n", getpid(), getppid());
execl("/bin/ps", "ps", "-f", (char *)0);
}
wait(NULL);
exit(0);
}

6. 信号

  • 信号是系统响应某个条件而产生的事件,进程接收到信号会执行相应的操作
  • 通过signal()设置相应的响应方式,可以设置以下几种响应方式
    • 默认SIG_DEF
    • 忽略SIG_IGN
    • 自定义 void fun_sig(int sig)

7. 实现自己的KILL命令

  • kill 默认信号量是15
  • 9号信号是不允许忽略的终止信号
  • sscanf解析转化字符串
1
2
3
4
5
6
7
#include <stdio.h>
int sscanf(const char *str, const char *format, ...);
//例程
int year, month, day;

int converted = sscanf("20191103", "%04d%02d%02d", &year, &month, &day);
printf("converted=%d, year=%d, month=%d, day=%d/n", converted, year, month, day);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc, char *argv[])
{
if (argc != 3)
{
printf("argc error\n");
exit(0);
}
int pid = 0;
int s = 0;
sscanf(argv[1], "%d", &pid);
sscanf(argv[2], "%d", &s);
printf("pid:%d signal:%d\n", pid, s);
kill(pid, s);
exit(0);
}

8. 有名管道和无名管道

  • 进程间通信方式:
    • 管道,半双工
      • 有名管道
      • 无名管道
    • 信号量
    • 共享内存
    • 消息队列
    • 套接字
  • 有/无名管道区别:
    • 有名管道在任意两个进程间通信
    • 无名管道在父子进程间通信
  • 有名管道创建使用
1
2
3
chengs@ubuntu:~/test$ mkfifo fifo
chengs@ubuntu:~/test$ ls -l
prw-rw-r-- 1 chengs chengs 0 Jun 18 04:00 fifo
  • 管道只有O_RDONLY、O_WRONLY两种模式
  • 管道文件只能两个进程同时打开,单个进程打开管道会阻塞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//write
int main()
{
int fd = open("fifo", O_WRONLY);
assert(fd != -1);
printf("fd=%d\n", fd);
while(1)
{
printf("input:\n");
char buff[128] = {0};
fgets(buff,128, stdin);
if ( strncmp(buff, "end", 3 ) == 0 )
{
break;
}
write(fd, buff, strlen(buff));
}
close(fd);
}
//read
int main()
{
int fd = open("fifo", O_RDONLY);
assert(fd != -1);
printf("fd=%d\n", fd);
char buff[128] = {0};
while (1)
{
if (read(fd, buff, 127) == 0 )
{
break;
}
printf("read: %s", buff);
}
close(fd);
}

  • 无名管道创建使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
int fd[2];
assert( pipe(fd) != -1); //fd[0] r, fd[1] w

pid_t pid = fork();
assert( pid != -1);

if(pid == 0)
{
close(fd[1]);
char buff[128] = {0};
read(fd[0], buff, 127);
printf("child read:%s\n", buff);
close(fd[0]);
}
else
{
close(fd[0]);
printf("parent write:hello\n");
write(fd[1], "hello", 5);
close(fd[1]);
}

9.信号量

信号量值代表允许访问的资源数目

  • P 操作
  • V 操作
  • 临界资源:同一时刻,只允许被一个进程或线程访问的资源
  • 临界区:访问临界资源的代码段
  • ipcs/ipcrm命令查看和删除消息进程通信方式(消息队列、共享内存、信号量)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//函数
int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, unsigned nsops);
int semctl(int semid, int semnum, int cmd, ...);
// sem.h
union semun{
int val;
};

void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
// sem.c
#include "sem.h"

static int semid = -1;

void sem_init(){
semid = semget((key_t)1234, 1, IPC_CREAT|IPC_EXCL|0600);
if(semid == -1){
semid = semget((key_t)1234, 1, 0600);
}else{
union semun a;
a.val = 1;//信号量初始值
if( semctl(semid, 0, SETVAL, a) == -1 ){
perror("semctrl error");
}
}
}
void sem_p(){
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;

if( semop(semid, &buf, 1) == -1 ){
perror("semop p error");
}
}
void sem_v(){
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;

if( semop(semid, &buf, 1) == -1 ){
perror("semop v error");
}
}
void sem_destroy(){
if( semctl(semid, 0, IPC_RMID) == -1 ){
perror("semctrl del error");
}
}
//a .c
#include "sem.h"

int main()
{
sem_init();
for (size_t i = 0; i < 10; i++)
{
sem_p();
printf("a");
fflush(stdout);
int n = rand()%3;
sleep(n);
printf("a");
fflush(stdout);
sem_v();
n = rand()%3;
sleep(n);
}
}
// b.c
#include "sem.h"

int main()
{
sem_init();
for (size_t i = 0; i < 10; i++)
{
sem_p();
printf("b");
fflush(stdout);
int n = rand()%3;
sleep(n);
printf("b");
fflush(stdout);
sem_v();
n = rand()%3;
sleep(n);
}
}

10.共享内存

  • 共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中
  • 并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
int shmget(key_t key, size_t size, int shmflg);
void* shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

// sem.h
#ifndef _SEM_H_
#define _SEM_H_

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/sem.h>

union semun{
int val;
};

int sem_init(int key, int semVal[], int nsems);
void sem_p(int semid, int index);
void sem_v(int semid, int index);
void sem_destroy(int semid);
#endif
// sem.c
#include "sem.h"

int sem_init(int key, int semVal[], int nsems){
int semid = semget((key_t)key, nsems, IPC_CREAT|IPC_EXCL|0664);
if(semid == -1){
semid = semget((key_t)key, nsems, 0664);
}else{
union semun a;
for (size_t i = 0; i < nsems; i++)
{
a.val = semVal[i];
if( semctl(semid, i, SETVAL, a) == -1 ){
perror("semctrl error");
}
}
}
return semid;
}
void sem_p(int semid, int index){
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;

if( semop(semid, &buf, 1) == -1 ){
perror("semop p error");
}
}
void sem_v(int semid, int index){
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;

if( semop(semid, &buf, 1) == -1 ){
perror("semop v error");
}
}
void sem_destroy(int semid){
if( semctl(semid, 0, IPC_RMID) == -1 ){
perror("semctrl del error");
}
}
// a.c
#include "sem.h"
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
int shmid = shmget((key_t)1234, 128, IPC_CREAT|0664);
assert(shmid != -1);

int sem[2] = {1, 0};
int semid = sem_init(1234, sem, 2);

char *p = (char *)shmat(shmid, NULL, 0);
assert(p != (char*)-1);

while (1)
{
char buff[128] = {0};
printf("please input:");
fgets(buff, 128, stdin);
sem_p(semid, 0);
strcpy(p, buff);
sem_v(semid, 1);
if(strncmp(buff, "end", 3) == 0){
break;
}
}
shmdt(p);
shmctl(shmid, IPC_RMID, NULL);
exit(0);
}

// b.c
#include "sem.h"
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
int shmid = shmget((key_t)1234, 128, IPC_CREAT|0664);
assert(shmid != -1);

int sem[2] = {1, 0};
int semid = sem_init(1234, sem, 2);

char *p = (char *)shmat(shmid, NULL, 0);
assert(p != (char*)-1);

while (1)
{
char buff[128] = {0};

sem_p(semid, 1);
strcpy(buff, p);
if(strncmp(buff, "end", 3) == 0){
break;
}
printf("sent:%s", buff);
fflush(stdout);
sem_v(semid, 0);

}
shmdt(p);
shmctl(shmid, IPC_RMID, NULL);
sem_destroy(semid);
exit(0);
}

11.消息队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
int msgget(key_t key, int msqflg);
int msgsnd(int msqid, const void *msqp, size_t msqsz, int msqflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

// a.c
typedef struct msgdata{
long mtype;
char mtext[128];
}MsgData;

int main()
{
int msgid = msgget((key_t)1234, IPC_CREAT|0664);
assert(msgid != -1);

MsgData data;
memset(&data, 0, sizeof(data));
data.mtype = 1;
strcpy(data.mtext, "hello");
msgsnd(msgid, &data, 128, 0);

exit(0);
}
// b.c
typedef struct msgdata{
long mtype;
char mtext[128];
}MsgData;

int main()
{
int msgid = msgget((key_t)1234, IPC_CREAT|0664);
assert(msgid != -1);

MsgData data;
memset(&data, 0, sizeof(data));

msgrcv(msgid, &data, 128, 1, 0);
printf("id:%d, text:%s", (int)data.mtype, data.mtext);
msgctl(msgid, IPC_RMID, NULL);
exit(0);
}

12.线程

  • 在操作系统中线程的实现有三种方式
    • 内核级线程
    • 用户级线程
    • 组合级线程
  • 进程与线程的区别
    • 进程是资源分配的最小单位,线程是 CPU 调度的最小单位
    • 进程有自己的独立地址空间,线程共享进程中的地址空间
    • 进程的创建消耗资源大,线程的创建相对较小
    • 进程的切换开销大,线程的切换开销相对较小
  • 线程接口
    • pthread不是 Linux 系统默认的库,编译中要加 -lpthread参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
int pthread_exit(void *retval);
int pthread_join(pthread_t thread, void **retval);

// 例程
void * fun(void * argv){
int *threadNum = (int*)argv;
for (size_t i = 0; i < 5; i++)
{
sleep(1);
printf("this thread is %d\n", *threadNum);
}
pthread_exit(threadNum);
}

int main()
{
pthread_t t;
int *ret = NULL;
int a = 1;
assert( pthread_create(&t, NULL, fun, &a) != -1 );
assert( pthread_join(t, (void **)&ret) != -1 );

printf("return: %d\n", *ret);
exit(0);
}

13.线程同步

  • 互斥锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//例程
pthread_mutex_t mutex;

void random_sleep(){
int n = rand()%3;
sleep(n);
}
void * fun(void * argv){
for (size_t i = 0; i < 5; i++)
{
pthread_mutex_lock(&mutex);
printf("A");
fflush(stdout);
random_sleep();
printf("A\n");
pthread_mutex_unlock(&mutex);
random_sleep();
}
}

int main()
{
pthread_t t;
int *ret = NULL;
int a = 1;

pthread_mutex_init(&mutex, NULL);

assert( pthread_create(&t, NULL, fun, &a) != -1 );

for (size_t i = 0; i < 5; i++)
{
pthread_mutex_lock(&mutex);
printf("B");
fflush(stdout);
random_sleep();
printf("B\n");
pthread_mutex_unlock(&mutex);
random_sleep();
}
pthread_mutex_destroy(&mutex);
exit(0);
}
  • 信号量
1
2
3
4
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_destroy(sem_t *sem);
  • 条件变量
    • 条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程
1
2
3
4
5
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个线程
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有等待的线程
int pthread_cond_destroy(pthread_cond_t *cond);
  • 读写锁
1
2
3
4
5
int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

14.线程安全

  • 对线程同步,保证同一时刻只有一个线程访问临界资源。
  • 在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个
    函数能被多个线程同时调用且不发生竟态条件,则我们称它是线程安全的。
  • char *strtok(char *str, const char *delim)//分割字符串
    • strtok就是线程不安全的,该函数内部存放一个指针静态变量,如果多线程同时使用该指针就会导致线程不安全
    • 采用strok_r函数是线程安全的

15.线程fork

  • 多线程种如果子线程fork,只会复制该子线程,其他子线程不会复制